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. */ 43static svn_error_t * 44hotcopy_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 */ 98static svn_error_t * 99entry_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. */ 120static svn_error_t * 121hotcopy_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 * If PROPS is set, copy the revprops file, otherwise copy the rev data file. 247 * Use SCRATCH_POOL for temporary allocations. */ 248static svn_error_t * 249hotcopy_copy_shard_file(svn_boolean_t *skipped_p, 250 const char *src_subdir, 251 const char *dst_subdir, 252 svn_revnum_t rev, 253 int max_files_per_dir, 254 svn_boolean_t props, 255 apr_pool_t *scratch_pool) 256{ 257 const char *src_subdir_shard = src_subdir, 258 *dst_subdir_shard = dst_subdir; 259 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); 264 265 if (rev % max_files_per_dir == 0) 266 { 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, 269 scratch_pool)); 270 } 271 272 SVN_ERR(hotcopy_io_dir_file_copy(skipped_p, 273 src_subdir_shard, dst_subdir_shard, 274 apr_psprintf(scratch_pool, "%c%ld", 275 props ? 'p' : 'r', 276 rev), 277 scratch_pool)); 278 return SVN_NO_ERROR; 279} 280 281 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. */ 290static svn_error_t * 291hotcopy_copy_packed_shard(svn_boolean_t *skipped_p, 292 svn_revnum_t *dst_min_unpacked_rev, 293 svn_fs_t *src_fs, 294 svn_fs_t *dst_fs, 295 svn_revnum_t rev, 296 int max_files_per_dir, 297 apr_pool_t *scratch_pool) 298{ 299 const char *src_subdir; 300 const char *dst_subdir; 301 const char *packed_shard; 302 const char *src_subdir_packed_shard; 303 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, 310 scratch_pool); 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, 315 scratch_pool)); 316 317 /* If necessary, update the min-unpacked rev file in the hotcopy. */ 318 if (*dst_min_unpacked_rev < rev + max_files_per_dir) 319 { 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, 323 scratch_pool)); 324 } 325 326 return SVN_NO_ERROR; 327} 328 329/* Remove file PATH, if it exists - even if it is read-only. 330 * Use SCRATCH_POOL for temporary allocations. */ 331static svn_error_t * 332hotcopy_remove_file(const char *path, 333 apr_pool_t *scratch_pool) 334{ 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)); 338 339 return SVN_NO_ERROR; 340} 341 342/* Verify that DST_FS is a suitable destination for an incremental 343 * hotcopy from SRC_FS. */ 344static svn_error_t * 345hotcopy_incremental_check_preconditions(svn_fs_t *src_fs, 346 svn_fs_t *dst_fs) 347{ 348 svn_fs_x__data_t *src_ffd = src_fs->fsap_data; 349 svn_fs_x__data_t *dst_ffd = dst_fs->fsap_data; 350 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); 358 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 " 365 "destination")); 366 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")); 374 return SVN_NO_ERROR; 375} 376 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. 384 */ 385static svn_error_t * 386hotcopy_revisions(svn_fs_t *src_fs, 387 svn_fs_t *dst_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, 394 void* notify_baton, 395 svn_cancel_func_t cancel_func, 396 void* cancel_baton, 397 apr_pool_t *scratch_pool) 398{ 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; 403 svn_revnum_t rev; 404 apr_pool_t *iterpool; 405 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, 408 scratch_pool)); 409 SVN_ERR(svn_fs_x__read_min_unpacked_rev(&dst_min_unpacked_rev, dst_fs, 410 scratch_pool)); 411 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); 423 424 SVN_ERR(svn_io_dir_file_copy(src_fs->path, dst_fs->path, 425 PATH_MIN_UNPACKED_REV, scratch_pool)); 426 427 if (cancel_func) 428 SVN_ERR(cancel_func(cancel_baton)); 429 430 /* 431 * Copy the necessary rev files. 432 */ 433 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) 437 { 438 svn_boolean_t skipped = TRUE; 439 svn_revnum_t pack_end_rev; 440 441 svn_pool_clear(iterpool); 442 443 if (cancel_func) 444 SVN_ERR(cancel_func(cancel_baton)); 445 446 /* Copy the packed shard. */ 447 SVN_ERR(hotcopy_copy_packed_shard(&skipped, &dst_min_unpacked_rev, 448 src_fs, dst_fs, 449 rev, max_files_per_dir, 450 iterpool)); 451 452 pack_end_rev = rev + max_files_per_dir - 1; 453 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) 458 { 459 SVN_ERR(svn_fs_x__write_current(dst_fs, pack_end_rev, iterpool)); 460 } 461 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); 469 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)); 474 } 475 476 if (cancel_func) 477 SVN_ERR(cancel_func(cancel_baton)); 478 479 SVN_ERR_ASSERT(rev == src_min_unpacked_rev); 480 SVN_ERR_ASSERT(src_min_unpacked_rev == dst_min_unpacked_rev); 481 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++) 485 { 486 svn_boolean_t skipped = TRUE; 487 488 svn_pool_clear(iterpool); 489 490 if (cancel_func) 491 SVN_ERR(cancel_func(cancel_baton)); 492 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. 497 * 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). */ 504 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, 508 iterpool)); 509 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, 513 iterpool)); 514 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) 519 { 520 if (max_files_per_dir && (rev % max_files_per_dir == 0)) 521 { 522 SVN_ERR(svn_fs_x__write_current(dst_fs, rev, iterpool)); 523 } 524 } 525 526 if (notify_func && !skipped) 527 notify_func(notify_baton, rev, rev, iterpool); 528 } 529 svn_pool_destroy(iterpool); 530 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); 534 535 return SVN_NO_ERROR; 536} 537 538/* Baton for hotcopy_body(). */ 539typedef struct hotcopy_body_baton_t { 540 svn_fs_t *src_fs; 541 svn_fs_t *dst_fs; 542 svn_boolean_t incremental; 543 svn_fs_hotcopy_notify_t notify_func; 544 void *notify_baton; 545 svn_cancel_func_t cancel_func; 546 void *cancel_baton; 547} hotcopy_body_baton_t; 548 549/* Perform a hotcopy, either normal or incremental. 550 * 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. 554 * 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. 562 * 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. 566 */ 567static svn_error_t * 568hotcopy_body(void *baton, 569 apr_pool_t *scratch_pool) 570{ 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; 586 587 /* Try to copy the config. 588 * 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, 594 scratch_pool)); 595 596 if (cancel_func) 597 SVN_ERR(cancel_func(cancel_baton)); 598 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)); 605 if (incremental) 606 { 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); 614 } 615 else 616 dst_youngest = 0; 617 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); 620 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)); 624 if (cancel_func) 625 SVN_ERR(cancel_func(cancel_baton)); 626 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)); 636 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, 643 scratch_pool)); 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, 650 scratch_pool)); 651 652 /* 653 * NB: Data copied below is only read by writers, not readers. 654 * Writers are still locked out at this point. 655 */ 656 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) 663 { 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)); 667 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, 672 scratch_pool)); 673 } 674 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)); 678 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)); 684 685 /* Hotcopied FS is complete. Stamp it with a format file. */ 686 SVN_ERR(svn_fs_x__write_format(dst_fs, TRUE, scratch_pool)); 687 688 return SVN_NO_ERROR; 689} 690 691svn_error_t * 692svn_fs_x__hotcopy(svn_fs_t *src_fs, 693 svn_fs_t *dst_fs, 694 const char *src_path, 695 const char *dst_path, 696 svn_boolean_t incremental, 697 svn_fs_hotcopy_notify_t notify_func, 698 void *notify_baton, 699 svn_cancel_func_t cancel_func, 700 void *cancel_baton, 701 svn_mutex__t *common_pool_lock, 702 apr_pool_t *scratch_pool, 703 apr_pool_t *common_pool) 704{ 705 hotcopy_body_baton_t hbb; 706 707 if (cancel_func) 708 SVN_ERR(cancel_func(cancel_baton)); 709 710 SVN_ERR(svn_fs_x__open(src_fs, src_path, scratch_pool)); 711 712 if (incremental) 713 { 714 const char *dst_format_abspath; 715 svn_node_kind_t dst_format_kind; 716 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, 720 scratch_pool); 721 SVN_ERR(svn_io_check_path(dst_format_abspath, &dst_format_kind, 722 scratch_pool)); 723 if (dst_format_kind == svn_node_none) 724 { 725 /* No destination? Fallback to a non-incremental hotcopy. */ 726 incremental = FALSE; 727 } 728 } 729 730 if (incremental) 731 { 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)); 735 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)); 739 } 740 else 741 { 742 /* Start out with an empty destination using the same configuration 743 * as the source. */ 744 svn_fs_x__data_t *src_ffd = src_fs->fsap_data; 745 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, 749 scratch_pool)); 750 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, 754 scratch_pool)); 755 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, 759 scratch_pool), 760 scratch_pool)); 761 SVN_ERR(hotcopy_remove_file(svn_fs_x__path_revprops(dst_fs, 0, 762 scratch_pool), 763 scratch_pool)); 764 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)); 768 } 769 770 if (cancel_func) 771 SVN_ERR(cancel_func(cancel_baton)); 772 773 hbb.src_fs = src_fs; 774 hbb.dst_fs = dst_fs; 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; 780 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 784 * concurrency. */ 785 if (incremental) 786 SVN_ERR(svn_fs_x__with_all_locks(dst_fs, hotcopy_body, &hbb, 787 scratch_pool)); 788 else 789 SVN_ERR(hotcopy_body(&hbb, scratch_pool)); 790 791 return SVN_NO_ERROR; 792} 793