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