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 * If PROPS is set, copy the revprops file, otherwise copy the rev data file.
247 * Use SCRATCH_POOL for temporary allocations. */
249 hotcopy_copy_shard_file(svn_boolean_t *skipped_p,
250 const char *src_subdir,
251 const char *dst_subdir,
253 int max_files_per_dir,
255 apr_pool_t *scratch_pool)
257 const char *src_subdir_shard = src_subdir,
258 *dst_subdir_shard = dst_subdir;
260 const char *shard = apr_psprintf(scratch_pool, "%ld",
261 rev / max_files_per_dir);
262 src_subdir_shard = svn_dirent_join(src_subdir, shard, scratch_pool);
263 dst_subdir_shard = svn_dirent_join(dst_subdir, shard, scratch_pool);
265 if (rev % max_files_per_dir == 0)
267 SVN_ERR(svn_io_make_dir_recursively(dst_subdir_shard, scratch_pool));
268 SVN_ERR(svn_io_copy_perms(dst_subdir, dst_subdir_shard,
272 SVN_ERR(hotcopy_io_dir_file_copy(skipped_p,
273 src_subdir_shard, dst_subdir_shard,
274 apr_psprintf(scratch_pool, "%c%ld",
282 /* Copy a packed shard containing revision REV, and which contains
283 * MAX_FILES_PER_DIR revisions, from SRC_FS to DST_FS.
284 * Update *DST_MIN_UNPACKED_REV in case the shard is new in DST_FS.
285 * Do not re-copy data which already exists in DST_FS.
286 * Set *SKIPPED_P to FALSE only if at least one part of the shard
287 * was copied, do not change the value in *SKIPPED_P otherwise.
288 * SKIPPED_P may be NULL if not required.
289 * Use SCRATCH_POOL for temporary allocations. */
291 hotcopy_copy_packed_shard(svn_boolean_t *skipped_p,
292 svn_revnum_t *dst_min_unpacked_rev,
296 int max_files_per_dir,
297 apr_pool_t *scratch_pool)
299 const char *src_subdir;
300 const char *dst_subdir;
301 const char *packed_shard;
302 const char *src_subdir_packed_shard;
304 /* Copy the packed shard. */
305 src_subdir = svn_dirent_join(src_fs->path, PATH_REVS_DIR, scratch_pool);
306 dst_subdir = svn_dirent_join(dst_fs->path, PATH_REVS_DIR, scratch_pool);
307 packed_shard = apr_psprintf(scratch_pool, "%ld" PATH_EXT_PACKED_SHARD,
308 rev / max_files_per_dir);
309 src_subdir_packed_shard = svn_dirent_join(src_subdir, packed_shard,
311 SVN_ERR(hotcopy_io_copy_dir_recursively(skipped_p, src_subdir_packed_shard,
312 dst_subdir, packed_shard,
313 TRUE /* copy_perms */,
314 NULL /* cancel_func */, NULL,
317 /* If necessary, update the min-unpacked rev file in the hotcopy. */
318 if (*dst_min_unpacked_rev < rev + max_files_per_dir)
320 *dst_min_unpacked_rev = rev + max_files_per_dir;
321 SVN_ERR(svn_fs_x__write_min_unpacked_rev(dst_fs,
322 *dst_min_unpacked_rev,
329 /* Remove file PATH, if it exists - even if it is read-only.
330 * Use SCRATCH_POOL for temporary allocations. */
332 hotcopy_remove_file(const char *path,
333 apr_pool_t *scratch_pool)
335 /* Make the rev file writable and remove it. */
336 SVN_ERR(svn_io_set_file_read_write(path, TRUE, scratch_pool));
337 SVN_ERR(svn_io_remove_file2(path, TRUE, scratch_pool));
342 /* Verify that DST_FS is a suitable destination for an incremental
343 * hotcopy from SRC_FS. */
345 hotcopy_incremental_check_preconditions(svn_fs_t *src_fs,
348 svn_fs_x__data_t *src_ffd = src_fs->fsap_data;
349 svn_fs_x__data_t *dst_ffd = dst_fs->fsap_data;
351 /* We only support incremental hotcopy between the same format. */
352 if (src_ffd->format != dst_ffd->format)
353 return svn_error_createf(SVN_ERR_UNSUPPORTED_FEATURE, NULL,
354 _("The FSX format (%d) of the hotcopy source does not match the "
355 "FSX format (%d) of the hotcopy destination; please upgrade "
356 "both repositories to the same format"),
357 src_ffd->format, dst_ffd->format);
359 /* Make sure the UUID of source and destination match up.
360 * We don't want to copy over a different repository. */
361 if (strcmp(src_fs->uuid, dst_fs->uuid) != 0)
362 return svn_error_create(SVN_ERR_RA_UUID_MISMATCH, NULL,
363 _("The UUID of the hotcopy source does "
364 "not match the UUID of the hotcopy "
367 /* Also require same shard size. */
368 if (src_ffd->max_files_per_dir != dst_ffd->max_files_per_dir)
369 return svn_error_create(SVN_ERR_UNSUPPORTED_FEATURE, NULL,
370 _("The sharding layout configuration "
371 "of the hotcopy source does not match "
372 "the sharding layout configuration of "
373 "the hotcopy destination"));
377 /* Copy the revision and revprop files (possibly sharded / packed) from
378 * SRC_FS to DST_FS. Do not re-copy data which already exists in DST_FS.
379 * When copying packed or unpacked shards, checkpoint the result in DST_FS
380 * for every shard by updating the 'current' file if necessary. Assume
381 * the >= SVN_FS_FS__MIN_NO_GLOBAL_IDS_FORMAT filesystem format without
382 * global next-ID counters. Indicate progress via the optional NOTIFY_FUNC
383 * callback using NOTIFY_BATON. Use SCRATCH_POOL for temporary allocations.
386 hotcopy_revisions(svn_fs_t *src_fs,
388 svn_revnum_t src_youngest,
389 svn_revnum_t dst_youngest,
390 svn_boolean_t incremental,
391 const char *src_revs_dir,
392 const char *dst_revs_dir,
393 svn_fs_hotcopy_notify_t notify_func,
395 svn_cancel_func_t cancel_func,
397 apr_pool_t *scratch_pool)
399 svn_fs_x__data_t *src_ffd = src_fs->fsap_data;
400 int max_files_per_dir = src_ffd->max_files_per_dir;
401 svn_revnum_t src_min_unpacked_rev;
402 svn_revnum_t dst_min_unpacked_rev;
404 apr_pool_t *iterpool;
406 /* Copy the min unpacked rev, and read its value. */
407 SVN_ERR(svn_fs_x__read_min_unpacked_rev(&src_min_unpacked_rev, src_fs,
409 SVN_ERR(svn_fs_x__read_min_unpacked_rev(&dst_min_unpacked_rev, dst_fs,
412 /* We only support packs coming from the hotcopy source.
413 * The destination should not be packed independently from
414 * the source. This also catches the case where users accidentally
415 * swap the source and destination arguments. */
416 if (src_min_unpacked_rev < dst_min_unpacked_rev)
417 return svn_error_createf(SVN_ERR_UNSUPPORTED_FEATURE, NULL,
418 _("The hotcopy destination already contains "
419 "more packed revisions (%lu) than the "
420 "hotcopy source contains (%lu)"),
421 dst_min_unpacked_rev - 1,
422 src_min_unpacked_rev - 1);
424 SVN_ERR(svn_io_dir_file_copy(src_fs->path, dst_fs->path,
425 PATH_MIN_UNPACKED_REV, scratch_pool));
428 SVN_ERR(cancel_func(cancel_baton));
431 * Copy the necessary rev files.
434 iterpool = svn_pool_create(scratch_pool);
435 /* First, copy packed shards. */
436 for (rev = 0; rev < src_min_unpacked_rev; rev += max_files_per_dir)
438 svn_boolean_t skipped = TRUE;
439 svn_revnum_t pack_end_rev;
441 svn_pool_clear(iterpool);
444 SVN_ERR(cancel_func(cancel_baton));
446 /* Copy the packed shard. */
447 SVN_ERR(hotcopy_copy_packed_shard(&skipped, &dst_min_unpacked_rev,
449 rev, max_files_per_dir,
452 pack_end_rev = rev + max_files_per_dir - 1;
454 /* Whenever this pack did not previously exist in the destination,
455 * update 'current' to the most recent packed rev (so readers can see
456 * new revisions which arrived in this pack). */
457 if (pack_end_rev > dst_youngest)
459 SVN_ERR(svn_fs_x__write_current(dst_fs, pack_end_rev, iterpool));
462 /* When notifying about packed shards, make things simpler by either
463 * reporting a full revision range, i.e [pack start, pack end] or
464 * reporting nothing. There is one case when this approach might not
465 * be exact (incremental hotcopy with a pack replacing last unpacked
466 * revisions), but generally this is good enough. */
467 if (notify_func && !skipped)
468 notify_func(notify_baton, rev, pack_end_rev, iterpool);
470 /* Now that all revisions have moved into the pack, the original
471 * rev dir can be removed. */
472 SVN_ERR(svn_io_remove_dir2(svn_fs_x__path_shard(dst_fs, rev, iterpool),
473 TRUE, cancel_func, cancel_baton, iterpool));
477 SVN_ERR(cancel_func(cancel_baton));
479 SVN_ERR_ASSERT(rev == src_min_unpacked_rev);
480 SVN_ERR_ASSERT(src_min_unpacked_rev == dst_min_unpacked_rev);
482 /* Now, copy pairs of non-packed revisions and revprop files.
483 * If necessary, update 'current' after copying all files from a shard. */
484 for (; rev <= src_youngest; rev++)
486 svn_boolean_t skipped = TRUE;
488 svn_pool_clear(iterpool);
491 SVN_ERR(cancel_func(cancel_baton));
493 /* Copying non-packed revisions is racy in case the source repository is
494 * being packed concurrently with this hotcopy operation. With the pack
495 * lock, however, the race is impossible, because hotcopy and pack
496 * operations block each other.
498 * We assume that all revisions coming after 'min-unpacked-rev' really
499 * are unpacked and that's not necessarily true with concurrent packing.
500 * Don't try to be smart in this edge case, because handling it properly
501 * might require copying *everything* from the start. Just abort the
502 * hotcopy with an ENOENT (revision file moved to a pack, so it is no
503 * longer where we expect it to be). */
505 /* Copy the rev file. */
506 SVN_ERR(hotcopy_copy_shard_file(&skipped, src_revs_dir, dst_revs_dir,
507 rev, max_files_per_dir, FALSE,
510 /* Copy the revprop file. */
511 SVN_ERR(hotcopy_copy_shard_file(&skipped, src_revs_dir, dst_revs_dir,
512 rev, max_files_per_dir, TRUE,
515 /* Whenever this revision did not previously exist in the destination,
516 * checkpoint the progress via 'current' (do that once per full shard
517 * in order not to slow things down). */
518 if (rev > dst_youngest)
520 if (max_files_per_dir && (rev % max_files_per_dir == 0))
522 SVN_ERR(svn_fs_x__write_current(dst_fs, rev, iterpool));
526 if (notify_func && !skipped)
527 notify_func(notify_baton, rev, rev, iterpool);
529 svn_pool_destroy(iterpool);
531 /* We assume that all revisions were copied now, i.e. we didn't exit the
532 * above loop early. 'rev' was last incremented during exit of the loop. */
533 SVN_ERR_ASSERT(rev == src_youngest + 1);
538 /* Baton for hotcopy_body(). */
539 typedef struct hotcopy_body_baton_t {
542 svn_boolean_t incremental;
543 svn_fs_hotcopy_notify_t notify_func;
545 svn_cancel_func_t cancel_func;
547 } hotcopy_body_baton_t;
549 /* Perform a hotcopy, either normal or incremental.
551 * Normal hotcopy assumes that the destination exists as an empty
552 * directory. It behaves like an incremental hotcopy except that
553 * none of the copied files already exist in the destination.
555 * An incremental hotcopy copies only changed or new files to the destination,
556 * and removes files from the destination no longer present in the source.
557 * While the incremental hotcopy is running, readers should still be able
558 * to access the destination repository without error and should not see
559 * revisions currently in progress of being copied. Readers are able to see
560 * new fully copied revisions even if the entire incremental hotcopy procedure
561 * has not yet completed.
563 * Writers are blocked out completely during the entire incremental hotcopy
564 * process to ensure consistency. This function assumes that the repository
565 * write-lock is held.
568 hotcopy_body(void *baton,
569 apr_pool_t *scratch_pool)
571 hotcopy_body_baton_t *hbb = baton;
572 svn_fs_t *src_fs = hbb->src_fs;
573 svn_fs_t *dst_fs = hbb->dst_fs;
574 svn_boolean_t incremental = hbb->incremental;
575 svn_fs_hotcopy_notify_t notify_func = hbb->notify_func;
576 void* notify_baton = hbb->notify_baton;
577 svn_cancel_func_t cancel_func = hbb->cancel_func;
578 void* cancel_baton = hbb->cancel_baton;
579 svn_revnum_t src_youngest;
580 svn_revnum_t dst_youngest;
581 const char *src_revs_dir;
582 const char *dst_revs_dir;
583 const char *src_subdir;
584 const char *dst_subdir;
585 svn_node_kind_t kind;
587 /* Try to copy the config.
589 * ### We try copying the config file before doing anything else,
590 * ### because higher layers will abort the hotcopy if we throw
591 * ### an error from this function, and that renders the hotcopy
592 * ### unusable anyway. */
593 SVN_ERR(svn_io_dir_file_copy(src_fs->path, dst_fs->path, PATH_CONFIG,
597 SVN_ERR(cancel_func(cancel_baton));
599 /* Find the youngest revision in the source and destination.
600 * We only support hotcopies from sources with an equal or greater amount
601 * of revisions than the destination.
602 * This also catches the case where users accidentally swap the
603 * source and destination arguments. */
604 SVN_ERR(svn_fs_x__read_current(&src_youngest, src_fs, scratch_pool));
607 SVN_ERR(svn_fs_x__youngest_rev(&dst_youngest, dst_fs, scratch_pool));
608 if (src_youngest < dst_youngest)
609 return svn_error_createf(SVN_ERR_UNSUPPORTED_FEATURE, NULL,
610 _("The hotcopy destination already contains more revisions "
611 "(%lu) than the hotcopy source contains (%lu); are source "
612 "and destination swapped?"),
613 dst_youngest, src_youngest);
618 src_revs_dir = svn_dirent_join(src_fs->path, PATH_REVS_DIR, scratch_pool);
619 dst_revs_dir = svn_dirent_join(dst_fs->path, PATH_REVS_DIR, scratch_pool);
621 /* Ensure that the required folders exist in the destination
622 * before actually copying the revisions and revprops. */
623 SVN_ERR(svn_io_make_dir_recursively(dst_revs_dir, scratch_pool));
625 SVN_ERR(cancel_func(cancel_baton));
627 /* Split the logic for new and old FS formats. The latter is much simpler
628 * due to the absense of sharding and packing. However, it requires special
629 * care when updating the 'current' file (which contains not just the
630 * revision number, but also the next-ID counters). */
631 SVN_ERR(hotcopy_revisions(src_fs, dst_fs, src_youngest, dst_youngest,
632 incremental, src_revs_dir, dst_revs_dir,
633 notify_func, notify_baton,
634 cancel_func, cancel_baton, scratch_pool));
635 SVN_ERR(svn_fs_x__write_current(dst_fs, src_youngest, scratch_pool));
637 /* Replace the locks tree.
638 * This is racy in case readers are currently trying to list locks in
639 * the destination. However, we need to get rid of stale locks.
640 * This is the simplest way of doing this, so we accept this small race. */
641 dst_subdir = svn_dirent_join(dst_fs->path, PATH_LOCKS_DIR, scratch_pool);
642 SVN_ERR(svn_io_remove_dir2(dst_subdir, TRUE, cancel_func, cancel_baton,
644 src_subdir = svn_dirent_join(src_fs->path, PATH_LOCKS_DIR, scratch_pool);
645 SVN_ERR(svn_io_check_path(src_subdir, &kind, scratch_pool));
646 if (kind == svn_node_dir)
647 SVN_ERR(svn_io_copy_dir_recursively(src_subdir, dst_fs->path,
648 PATH_LOCKS_DIR, TRUE,
649 cancel_func, cancel_baton,
653 * NB: Data copied below is only read by writers, not readers.
654 * Writers are still locked out at this point.
657 /* Copy the rep cache and then remove entries for revisions
658 * younger than the destination's youngest revision. */
659 src_subdir = svn_dirent_join(src_fs->path, REP_CACHE_DB_NAME, scratch_pool);
660 dst_subdir = svn_dirent_join(dst_fs->path, REP_CACHE_DB_NAME, scratch_pool);
661 SVN_ERR(svn_io_check_path(src_subdir, &kind, scratch_pool));
662 if (kind == svn_node_file)
664 /* Copy the rep cache and then remove entries for revisions
665 * that did not make it into the destination. */
666 SVN_ERR(svn_sqlite__hotcopy(src_subdir, dst_subdir, scratch_pool));
668 /* The source might have r/o flags set on it - which would be
669 carried over to the copy. */
670 SVN_ERR(svn_io_set_file_read_write(dst_subdir, FALSE, scratch_pool));
671 SVN_ERR(svn_fs_x__del_rep_reference(dst_fs, src_youngest,
675 /* Copy the txn-current file. */
676 SVN_ERR(svn_io_dir_file_copy(src_fs->path, dst_fs->path,
677 PATH_TXN_CURRENT, scratch_pool));
679 /* If a revprop generation file exists in the source filesystem,
680 * reset it to zero (since this is on a different path, it will not
681 * overlap with data already in cache). Also, clean up stale files
682 * used for the named atomics implementation. */
683 SVN_ERR(svn_fs_x__reset_revprop_generation_file(dst_fs, scratch_pool));
685 /* Hotcopied FS is complete. Stamp it with a format file. */
686 SVN_ERR(svn_fs_x__write_format(dst_fs, TRUE, scratch_pool));
692 svn_fs_x__hotcopy(svn_fs_t *src_fs,
694 const char *src_path,
695 const char *dst_path,
696 svn_boolean_t incremental,
697 svn_fs_hotcopy_notify_t notify_func,
699 svn_cancel_func_t cancel_func,
701 svn_mutex__t *common_pool_lock,
702 apr_pool_t *scratch_pool,
703 apr_pool_t *common_pool)
705 hotcopy_body_baton_t hbb;
708 SVN_ERR(cancel_func(cancel_baton));
710 SVN_ERR(svn_fs_x__open(src_fs, src_path, scratch_pool));
714 const char *dst_format_abspath;
715 svn_node_kind_t dst_format_kind;
717 /* Check destination format to be sure we know how to incrementally
718 * hotcopy to the destination FS. */
719 dst_format_abspath = svn_dirent_join(dst_path, PATH_FORMAT,
721 SVN_ERR(svn_io_check_path(dst_format_abspath, &dst_format_kind,
723 if (dst_format_kind == svn_node_none)
725 /* No destination? Fallback to a non-incremental hotcopy. */
732 /* Check the existing repository. */
733 SVN_ERR(svn_fs_x__open(dst_fs, dst_path, scratch_pool));
734 SVN_ERR(hotcopy_incremental_check_preconditions(src_fs, dst_fs));
736 SVN_ERR(svn_fs_x__initialize_shared_data(dst_fs, common_pool_lock,
737 scratch_pool, common_pool));
738 SVN_ERR(svn_fs_x__initialize_caches(dst_fs, scratch_pool));
742 /* Start out with an empty destination using the same configuration
744 svn_fs_x__data_t *src_ffd = src_fs->fsap_data;
746 /* Create the DST_FS repository with the same layout as SRC_FS. */
747 SVN_ERR(svn_fs_x__create_file_tree(dst_fs, dst_path, src_ffd->format,
748 src_ffd->max_files_per_dir,
751 /* Copy the UUID. Hotcopy destination receives a new instance ID, but
752 * has the same filesystem UUID as the source. */
753 SVN_ERR(svn_fs_x__set_uuid(dst_fs, src_fs->uuid, NULL, TRUE,
756 /* Remove revision 0 contents. Otherwise, it may not get overwritten
757 * due to having a newer timestamp. */
758 SVN_ERR(hotcopy_remove_file(svn_fs_x__path_rev(dst_fs, 0,
761 SVN_ERR(hotcopy_remove_file(svn_fs_x__path_revprops(dst_fs, 0,
765 SVN_ERR(svn_fs_x__initialize_shared_data(dst_fs, common_pool_lock,
766 scratch_pool, common_pool));
767 SVN_ERR(svn_fs_x__initialize_caches(dst_fs, scratch_pool));
771 SVN_ERR(cancel_func(cancel_baton));
775 hbb.incremental = incremental;
776 hbb.notify_func = notify_func;
777 hbb.notify_baton = notify_baton;
778 hbb.cancel_func = cancel_func;
779 hbb.cancel_baton = cancel_baton;
781 /* Lock the destination in the incremental mode. For a non-incremental
782 * hotcopy, don't take any locks. In that case the destination cannot be
783 * opened until the hotcopy finishes, and we don't have to worry about
786 SVN_ERR(svn_fs_x__with_all_locks(dst_fs, hotcopy_body, &hbb,
789 SVN_ERR(hotcopy_body(&hbb, scratch_pool));