dump.c revision 269847
1/* dump.c --- writing filesystem contents into a portable 'dumpfile' format. 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 23 24#include "svn_private_config.h" 25#include "svn_pools.h" 26#include "svn_error.h" 27#include "svn_fs.h" 28#include "svn_hash.h" 29#include "svn_iter.h" 30#include "svn_repos.h" 31#include "svn_string.h" 32#include "svn_dirent_uri.h" 33#include "svn_path.h" 34#include "svn_time.h" 35#include "svn_checksum.h" 36#include "svn_props.h" 37#include "svn_sorts.h" 38 39#include "private/svn_mergeinfo_private.h" 40#include "private/svn_fs_private.h" 41 42#define ARE_VALID_COPY_ARGS(p,r) ((p) && SVN_IS_VALID_REVNUM(r)) 43 44/*----------------------------------------------------------------------*/ 45 46 47 48/* Compute the delta between OLDROOT/OLDPATH and NEWROOT/NEWPATH and 49 store it into a new temporary file *TEMPFILE. OLDROOT may be NULL, 50 in which case the delta will be computed against an empty file, as 51 per the svn_fs_get_file_delta_stream docstring. Record the length 52 of the temporary file in *LEN, and rewind the file before 53 returning. */ 54static svn_error_t * 55store_delta(apr_file_t **tempfile, svn_filesize_t *len, 56 svn_fs_root_t *oldroot, const char *oldpath, 57 svn_fs_root_t *newroot, const char *newpath, apr_pool_t *pool) 58{ 59 svn_stream_t *temp_stream; 60 apr_off_t offset = 0; 61 svn_txdelta_stream_t *delta_stream; 62 svn_txdelta_window_handler_t wh; 63 void *whb; 64 65 /* Create a temporary file and open a stream to it. Note that we need 66 the file handle in order to rewind it. */ 67 SVN_ERR(svn_io_open_unique_file3(tempfile, NULL, NULL, 68 svn_io_file_del_on_pool_cleanup, 69 pool, pool)); 70 temp_stream = svn_stream_from_aprfile2(*tempfile, TRUE, pool); 71 72 /* Compute the delta and send it to the temporary file. */ 73 SVN_ERR(svn_fs_get_file_delta_stream(&delta_stream, oldroot, oldpath, 74 newroot, newpath, pool)); 75 svn_txdelta_to_svndiff3(&wh, &whb, temp_stream, 0, 76 SVN_DELTA_COMPRESSION_LEVEL_DEFAULT, pool); 77 SVN_ERR(svn_txdelta_send_txstream(delta_stream, wh, whb, pool)); 78 79 /* Get the length of the temporary file and rewind it. */ 80 SVN_ERR(svn_io_file_seek(*tempfile, APR_CUR, &offset, pool)); 81 *len = offset; 82 offset = 0; 83 return svn_io_file_seek(*tempfile, APR_SET, &offset, pool); 84} 85 86 87/*----------------------------------------------------------------------*/ 88 89/** An editor which dumps node-data in 'dumpfile format' to a file. **/ 90 91/* Look, mom! No file batons! */ 92 93struct edit_baton 94{ 95 /* The relpath which implicitly prepends all full paths coming into 96 this editor. This will almost always be "". */ 97 const char *path; 98 99 /* The stream to dump to. */ 100 svn_stream_t *stream; 101 102 /* Send feedback here, if non-NULL */ 103 svn_repos_notify_func_t notify_func; 104 void *notify_baton; 105 106 /* The fs revision root, so we can read the contents of paths. */ 107 svn_fs_root_t *fs_root; 108 svn_revnum_t current_rev; 109 110 /* The fs, so we can grab historic information if needed. */ 111 svn_fs_t *fs; 112 113 /* True if dumped nodes should output deltas instead of full text. */ 114 svn_boolean_t use_deltas; 115 116 /* True if this "dump" is in fact a verify. */ 117 svn_boolean_t verify; 118 119 /* The first revision dumped in this dumpstream. */ 120 svn_revnum_t oldest_dumped_rev; 121 122 /* If not NULL, set to true if any references to revisions older than 123 OLDEST_DUMPED_REV were found in the dumpstream. */ 124 svn_boolean_t *found_old_reference; 125 126 /* If not NULL, set to true if any mergeinfo was dumped which contains 127 revisions older than OLDEST_DUMPED_REV. */ 128 svn_boolean_t *found_old_mergeinfo; 129 130 /* reusable buffer for writing file contents */ 131 char buffer[SVN__STREAM_CHUNK_SIZE]; 132 apr_size_t bufsize; 133}; 134 135struct dir_baton 136{ 137 struct edit_baton *edit_baton; 138 struct dir_baton *parent_dir_baton; 139 140 /* is this directory a new addition to this revision? */ 141 svn_boolean_t added; 142 143 /* has this directory been written to the output stream? */ 144 svn_boolean_t written_out; 145 146 /* the repository relpath associated with this directory */ 147 const char *path; 148 149 /* The comparison repository relpath and revision of this directory. 150 If both of these are valid, use them as a source against which to 151 compare the directory instead of the default comparison source of 152 PATH in the previous revision. */ 153 const char *cmp_path; 154 svn_revnum_t cmp_rev; 155 156 /* hash of paths that need to be deleted, though some -might- be 157 replaced. maps const char * paths to this dir_baton. (they're 158 full paths, because that's what the editor driver gives us. but 159 really, they're all within this directory.) */ 160 apr_hash_t *deleted_entries; 161 162 /* pool to be used for deleting the hash items */ 163 apr_pool_t *pool; 164}; 165 166 167/* Make a directory baton to represent the directory was path 168 (relative to EDIT_BATON's path) is PATH. 169 170 CMP_PATH/CMP_REV are the path/revision against which this directory 171 should be compared for changes. If either is omitted (NULL for the 172 path, SVN_INVALID_REVNUM for the rev), just compare this directory 173 PATH against itself in the previous revision. 174 175 PARENT_DIR_BATON is the directory baton of this directory's parent, 176 or NULL if this is the top-level directory of the edit. ADDED 177 indicated if this directory is newly added in this revision. 178 Perform all allocations in POOL. */ 179static struct dir_baton * 180make_dir_baton(const char *path, 181 const char *cmp_path, 182 svn_revnum_t cmp_rev, 183 void *edit_baton, 184 void *parent_dir_baton, 185 svn_boolean_t added, 186 apr_pool_t *pool) 187{ 188 struct edit_baton *eb = edit_baton; 189 struct dir_baton *pb = parent_dir_baton; 190 struct dir_baton *new_db = apr_pcalloc(pool, sizeof(*new_db)); 191 const char *full_path; 192 193 /* A path relative to nothing? I don't think so. */ 194 SVN_ERR_ASSERT_NO_RETURN(!path || pb); 195 196 /* Construct the full path of this node. */ 197 if (pb) 198 full_path = svn_relpath_join(eb->path, path, pool); 199 else 200 full_path = apr_pstrdup(pool, eb->path); 201 202 /* Remove leading slashes from copyfrom paths. */ 203 if (cmp_path) 204 cmp_path = svn_relpath_canonicalize(cmp_path, pool); 205 206 new_db->edit_baton = eb; 207 new_db->parent_dir_baton = pb; 208 new_db->path = full_path; 209 new_db->cmp_path = cmp_path; 210 new_db->cmp_rev = cmp_rev; 211 new_db->added = added; 212 new_db->written_out = FALSE; 213 new_db->deleted_entries = apr_hash_make(pool); 214 new_db->pool = pool; 215 216 return new_db; 217} 218 219 220/* If the mergeinfo in MERGEINFO_STR refers to any revisions older than 221 * OLDEST_DUMPED_REV, issue a warning and set *FOUND_OLD_MERGEINFO to TRUE, 222 * otherwise leave *FOUND_OLD_MERGEINFO unchanged. 223 */ 224static svn_error_t * 225verify_mergeinfo_revisions(svn_boolean_t *found_old_mergeinfo, 226 const char *mergeinfo_str, 227 svn_revnum_t oldest_dumped_rev, 228 svn_repos_notify_func_t notify_func, 229 void *notify_baton, 230 apr_pool_t *pool) 231{ 232 svn_mergeinfo_t mergeinfo, old_mergeinfo; 233 234 SVN_ERR(svn_mergeinfo_parse(&mergeinfo, mergeinfo_str, pool)); 235 SVN_ERR(svn_mergeinfo__filter_mergeinfo_by_ranges( 236 &old_mergeinfo, mergeinfo, 237 oldest_dumped_rev - 1, 0, 238 TRUE, pool, pool)); 239 240 if (apr_hash_count(old_mergeinfo)) 241 { 242 svn_repos_notify_t *notify = 243 svn_repos_notify_create(svn_repos_notify_warning, pool); 244 245 notify->warning = svn_repos_notify_warning_found_old_mergeinfo; 246 notify->warning_str = apr_psprintf( 247 pool, 248 _("Mergeinfo referencing revision(s) prior " 249 "to the oldest dumped revision (r%ld). " 250 "Loading this dump may result in invalid " 251 "mergeinfo."), 252 oldest_dumped_rev); 253 254 if (found_old_mergeinfo) 255 *found_old_mergeinfo = TRUE; 256 notify_func(notify_baton, notify, pool); 257 } 258 259 return SVN_NO_ERROR; 260} 261 262 263/* This helper is the main "meat" of the editor -- it does all the 264 work of writing a node record. 265 266 Write out a node record for PATH of type KIND under EB->FS_ROOT. 267 ACTION describes what is happening to the node (see enum svn_node_action). 268 Write record to writable EB->STREAM, using EB->BUFFER to write in chunks. 269 270 If the node was itself copied, IS_COPY is TRUE and the 271 path/revision of the copy source are in CMP_PATH/CMP_REV. If 272 IS_COPY is FALSE, yet CMP_PATH/CMP_REV are valid, this node is part 273 of a copied subtree. 274 */ 275static svn_error_t * 276dump_node(struct edit_baton *eb, 277 const char *path, 278 svn_node_kind_t kind, 279 enum svn_node_action action, 280 svn_boolean_t is_copy, 281 const char *cmp_path, 282 svn_revnum_t cmp_rev, 283 apr_pool_t *pool) 284{ 285 svn_stringbuf_t *propstring; 286 svn_filesize_t content_length = 0; 287 apr_size_t len; 288 svn_boolean_t must_dump_text = FALSE, must_dump_props = FALSE; 289 const char *compare_path = path; 290 svn_revnum_t compare_rev = eb->current_rev - 1; 291 svn_fs_root_t *compare_root = NULL; 292 apr_file_t *delta_file = NULL; 293 294 /* Maybe validate the path. */ 295 if (eb->verify || eb->notify_func) 296 { 297 svn_error_t *err = svn_fs__path_valid(path, pool); 298 299 if (err) 300 { 301 if (eb->notify_func) 302 { 303 char errbuf[512]; /* ### svn_strerror() magic number */ 304 svn_repos_notify_t *notify; 305 notify = svn_repos_notify_create(svn_repos_notify_warning, pool); 306 307 notify->warning = svn_repos_notify_warning_invalid_fspath; 308 notify->warning_str = apr_psprintf( 309 pool, 310 _("E%06d: While validating fspath '%s': %s"), 311 err->apr_err, path, 312 svn_err_best_message(err, errbuf, sizeof(errbuf))); 313 314 eb->notify_func(eb->notify_baton, notify, pool); 315 } 316 317 /* Return the error in addition to notifying about it. */ 318 if (eb->verify) 319 return svn_error_trace(err); 320 else 321 svn_error_clear(err); 322 } 323 } 324 325 /* Write out metadata headers for this file node. */ 326 SVN_ERR(svn_stream_printf(eb->stream, pool, 327 SVN_REPOS_DUMPFILE_NODE_PATH ": %s\n", 328 path)); 329 if (kind == svn_node_file) 330 SVN_ERR(svn_stream_puts(eb->stream, 331 SVN_REPOS_DUMPFILE_NODE_KIND ": file\n")); 332 else if (kind == svn_node_dir) 333 SVN_ERR(svn_stream_puts(eb->stream, 334 SVN_REPOS_DUMPFILE_NODE_KIND ": dir\n")); 335 336 /* Remove leading slashes from copyfrom paths. */ 337 if (cmp_path) 338 cmp_path = svn_relpath_canonicalize(cmp_path, pool); 339 340 /* Validate the comparison path/rev. */ 341 if (ARE_VALID_COPY_ARGS(cmp_path, cmp_rev)) 342 { 343 compare_path = cmp_path; 344 compare_rev = cmp_rev; 345 } 346 347 if (action == svn_node_action_change) 348 { 349 SVN_ERR(svn_stream_puts(eb->stream, 350 SVN_REPOS_DUMPFILE_NODE_ACTION ": change\n")); 351 352 /* either the text or props changed, or possibly both. */ 353 SVN_ERR(svn_fs_revision_root(&compare_root, 354 svn_fs_root_fs(eb->fs_root), 355 compare_rev, pool)); 356 357 SVN_ERR(svn_fs_props_changed(&must_dump_props, 358 compare_root, compare_path, 359 eb->fs_root, path, pool)); 360 if (kind == svn_node_file) 361 SVN_ERR(svn_fs_contents_changed(&must_dump_text, 362 compare_root, compare_path, 363 eb->fs_root, path, pool)); 364 } 365 else if (action == svn_node_action_replace) 366 { 367 if (! is_copy) 368 { 369 /* a simple delete+add, implied by a single 'replace' action. */ 370 SVN_ERR(svn_stream_puts(eb->stream, 371 SVN_REPOS_DUMPFILE_NODE_ACTION 372 ": replace\n")); 373 374 /* definitely need to dump all content for a replace. */ 375 if (kind == svn_node_file) 376 must_dump_text = TRUE; 377 must_dump_props = TRUE; 378 } 379 else 380 { 381 /* more complex: delete original, then add-with-history. */ 382 383 /* the path & kind headers have already been printed; just 384 add a delete action, and end the current record.*/ 385 SVN_ERR(svn_stream_puts(eb->stream, 386 SVN_REPOS_DUMPFILE_NODE_ACTION 387 ": delete\n\n")); 388 389 /* recurse: print an additional add-with-history record. */ 390 SVN_ERR(dump_node(eb, path, kind, svn_node_action_add, 391 is_copy, compare_path, compare_rev, pool)); 392 393 /* we can leave this routine quietly now, don't need to dump 394 any content; that was already done in the second record. */ 395 must_dump_text = FALSE; 396 must_dump_props = FALSE; 397 } 398 } 399 else if (action == svn_node_action_delete) 400 { 401 SVN_ERR(svn_stream_puts(eb->stream, 402 SVN_REPOS_DUMPFILE_NODE_ACTION ": delete\n")); 403 404 /* we can leave this routine quietly now, don't need to dump 405 any content. */ 406 must_dump_text = FALSE; 407 must_dump_props = FALSE; 408 } 409 else if (action == svn_node_action_add) 410 { 411 SVN_ERR(svn_stream_puts(eb->stream, 412 SVN_REPOS_DUMPFILE_NODE_ACTION ": add\n")); 413 414 if (! is_copy) 415 { 416 /* Dump all contents for a simple 'add'. */ 417 if (kind == svn_node_file) 418 must_dump_text = TRUE; 419 must_dump_props = TRUE; 420 } 421 else 422 { 423 if (!eb->verify && cmp_rev < eb->oldest_dumped_rev 424 && eb->notify_func) 425 { 426 svn_repos_notify_t *notify = 427 svn_repos_notify_create(svn_repos_notify_warning, pool); 428 429 notify->warning = svn_repos_notify_warning_found_old_reference; 430 notify->warning_str = apr_psprintf( 431 pool, 432 _("Referencing data in revision %ld," 433 " which is older than the oldest" 434 " dumped revision (r%ld). Loading this dump" 435 " into an empty repository" 436 " will fail."), 437 cmp_rev, eb->oldest_dumped_rev); 438 if (eb->found_old_reference) 439 *eb->found_old_reference = TRUE; 440 eb->notify_func(eb->notify_baton, notify, pool); 441 } 442 443 SVN_ERR(svn_stream_printf(eb->stream, pool, 444 SVN_REPOS_DUMPFILE_NODE_COPYFROM_REV 445 ": %ld\n" 446 SVN_REPOS_DUMPFILE_NODE_COPYFROM_PATH 447 ": %s\n", 448 cmp_rev, cmp_path)); 449 450 SVN_ERR(svn_fs_revision_root(&compare_root, 451 svn_fs_root_fs(eb->fs_root), 452 compare_rev, pool)); 453 454 /* Need to decide if the copied node had any extra textual or 455 property mods as well. */ 456 SVN_ERR(svn_fs_props_changed(&must_dump_props, 457 compare_root, compare_path, 458 eb->fs_root, path, pool)); 459 if (kind == svn_node_file) 460 { 461 svn_checksum_t *checksum; 462 const char *hex_digest; 463 SVN_ERR(svn_fs_contents_changed(&must_dump_text, 464 compare_root, compare_path, 465 eb->fs_root, path, pool)); 466 467 SVN_ERR(svn_fs_file_checksum(&checksum, svn_checksum_md5, 468 compare_root, compare_path, 469 FALSE, pool)); 470 hex_digest = svn_checksum_to_cstring(checksum, pool); 471 if (hex_digest) 472 SVN_ERR(svn_stream_printf(eb->stream, pool, 473 SVN_REPOS_DUMPFILE_TEXT_COPY_SOURCE_MD5 474 ": %s\n", hex_digest)); 475 476 SVN_ERR(svn_fs_file_checksum(&checksum, svn_checksum_sha1, 477 compare_root, compare_path, 478 FALSE, pool)); 479 hex_digest = svn_checksum_to_cstring(checksum, pool); 480 if (hex_digest) 481 SVN_ERR(svn_stream_printf(eb->stream, pool, 482 SVN_REPOS_DUMPFILE_TEXT_COPY_SOURCE_SHA1 483 ": %s\n", hex_digest)); 484 } 485 } 486 } 487 488 if ((! must_dump_text) && (! must_dump_props)) 489 { 490 /* If we're not supposed to dump text or props, so be it, we can 491 just go home. However, if either one needs to be dumped, 492 then our dumpstream format demands that at a *minimum*, we 493 see a lone "PROPS-END" as a divider between text and props 494 content within the content-block. */ 495 len = 2; 496 return svn_stream_write(eb->stream, "\n\n", &len); /* ### needed? */ 497 } 498 499 /*** Start prepping content to dump... ***/ 500 501 /* If we are supposed to dump properties, write out a property 502 length header and generate a stringbuf that contains those 503 property values here. */ 504 if (must_dump_props) 505 { 506 apr_hash_t *prophash, *oldhash = NULL; 507 apr_size_t proplen; 508 svn_stream_t *propstream; 509 510 SVN_ERR(svn_fs_node_proplist(&prophash, eb->fs_root, path, pool)); 511 512 /* If this is a partial dump, then issue a warning if we dump mergeinfo 513 properties that refer to revisions older than the first revision 514 dumped. */ 515 if (!eb->verify && eb->notify_func && eb->oldest_dumped_rev > 1) 516 { 517 svn_string_t *mergeinfo_str = svn_hash_gets(prophash, 518 SVN_PROP_MERGEINFO); 519 if (mergeinfo_str) 520 { 521 /* An error in verifying the mergeinfo must not prevent dumping 522 the data. Ignore any such error. */ 523 svn_error_clear(verify_mergeinfo_revisions( 524 eb->found_old_mergeinfo, 525 mergeinfo_str->data, eb->oldest_dumped_rev, 526 eb->notify_func, eb->notify_baton, 527 pool)); 528 } 529 } 530 531 if (eb->use_deltas && compare_root) 532 { 533 /* Fetch the old property hash to diff against and output a header 534 saying that our property contents are a delta. */ 535 SVN_ERR(svn_fs_node_proplist(&oldhash, compare_root, compare_path, 536 pool)); 537 SVN_ERR(svn_stream_puts(eb->stream, 538 SVN_REPOS_DUMPFILE_PROP_DELTA ": true\n")); 539 } 540 else 541 oldhash = apr_hash_make(pool); 542 propstring = svn_stringbuf_create_ensure(0, pool); 543 propstream = svn_stream_from_stringbuf(propstring, pool); 544 SVN_ERR(svn_hash_write_incremental(prophash, oldhash, propstream, 545 "PROPS-END", pool)); 546 SVN_ERR(svn_stream_close(propstream)); 547 proplen = propstring->len; 548 content_length += proplen; 549 SVN_ERR(svn_stream_printf(eb->stream, pool, 550 SVN_REPOS_DUMPFILE_PROP_CONTENT_LENGTH 551 ": %" APR_SIZE_T_FMT "\n", proplen)); 552 } 553 554 /* If we are supposed to dump text, write out a text length header 555 here, and an MD5 checksum (if available). */ 556 if (must_dump_text && (kind == svn_node_file)) 557 { 558 svn_checksum_t *checksum; 559 const char *hex_digest; 560 svn_filesize_t textlen; 561 562 if (eb->use_deltas) 563 { 564 /* Compute the text delta now and write it into a temporary 565 file, so that we can find its length. Output a header 566 saying our text contents are a delta. */ 567 SVN_ERR(store_delta(&delta_file, &textlen, compare_root, 568 compare_path, eb->fs_root, path, pool)); 569 SVN_ERR(svn_stream_puts(eb->stream, 570 SVN_REPOS_DUMPFILE_TEXT_DELTA ": true\n")); 571 572 if (compare_root) 573 { 574 SVN_ERR(svn_fs_file_checksum(&checksum, svn_checksum_md5, 575 compare_root, compare_path, 576 FALSE, pool)); 577 hex_digest = svn_checksum_to_cstring(checksum, pool); 578 if (hex_digest) 579 SVN_ERR(svn_stream_printf(eb->stream, pool, 580 SVN_REPOS_DUMPFILE_TEXT_DELTA_BASE_MD5 581 ": %s\n", hex_digest)); 582 583 SVN_ERR(svn_fs_file_checksum(&checksum, svn_checksum_sha1, 584 compare_root, compare_path, 585 FALSE, pool)); 586 hex_digest = svn_checksum_to_cstring(checksum, pool); 587 if (hex_digest) 588 SVN_ERR(svn_stream_printf(eb->stream, pool, 589 SVN_REPOS_DUMPFILE_TEXT_DELTA_BASE_SHA1 590 ": %s\n", hex_digest)); 591 } 592 } 593 else 594 { 595 /* Just fetch the length of the file. */ 596 SVN_ERR(svn_fs_file_length(&textlen, eb->fs_root, path, pool)); 597 } 598 599 content_length += textlen; 600 SVN_ERR(svn_stream_printf(eb->stream, pool, 601 SVN_REPOS_DUMPFILE_TEXT_CONTENT_LENGTH 602 ": %" SVN_FILESIZE_T_FMT "\n", textlen)); 603 604 SVN_ERR(svn_fs_file_checksum(&checksum, svn_checksum_md5, 605 eb->fs_root, path, FALSE, pool)); 606 hex_digest = svn_checksum_to_cstring(checksum, pool); 607 if (hex_digest) 608 SVN_ERR(svn_stream_printf(eb->stream, pool, 609 SVN_REPOS_DUMPFILE_TEXT_CONTENT_MD5 610 ": %s\n", hex_digest)); 611 612 SVN_ERR(svn_fs_file_checksum(&checksum, svn_checksum_sha1, 613 eb->fs_root, path, FALSE, pool)); 614 hex_digest = svn_checksum_to_cstring(checksum, pool); 615 if (hex_digest) 616 SVN_ERR(svn_stream_printf(eb->stream, pool, 617 SVN_REPOS_DUMPFILE_TEXT_CONTENT_SHA1 618 ": %s\n", hex_digest)); 619 } 620 621 /* 'Content-length:' is the last header before we dump the content, 622 and is the sum of the text and prop contents lengths. We write 623 this only for the benefit of non-Subversion RFC-822 parsers. */ 624 SVN_ERR(svn_stream_printf(eb->stream, pool, 625 SVN_REPOS_DUMPFILE_CONTENT_LENGTH 626 ": %" SVN_FILESIZE_T_FMT "\n\n", 627 content_length)); 628 629 /* Dump property content if we're supposed to do so. */ 630 if (must_dump_props) 631 { 632 len = propstring->len; 633 SVN_ERR(svn_stream_write(eb->stream, propstring->data, &len)); 634 } 635 636 /* Dump text content */ 637 if (must_dump_text && (kind == svn_node_file)) 638 { 639 svn_stream_t *contents; 640 641 if (delta_file) 642 { 643 /* Make sure to close the underlying file when the stream is 644 closed. */ 645 contents = svn_stream_from_aprfile2(delta_file, FALSE, pool); 646 } 647 else 648 SVN_ERR(svn_fs_file_contents(&contents, eb->fs_root, path, pool)); 649 650 SVN_ERR(svn_stream_copy3(contents, svn_stream_disown(eb->stream, pool), 651 NULL, NULL, pool)); 652 } 653 654 len = 2; 655 return svn_stream_write(eb->stream, "\n\n", &len); /* ### needed? */ 656} 657 658 659static svn_error_t * 660open_root(void *edit_baton, 661 svn_revnum_t base_revision, 662 apr_pool_t *pool, 663 void **root_baton) 664{ 665 *root_baton = make_dir_baton(NULL, NULL, SVN_INVALID_REVNUM, 666 edit_baton, NULL, FALSE, pool); 667 return SVN_NO_ERROR; 668} 669 670 671static svn_error_t * 672delete_entry(const char *path, 673 svn_revnum_t revision, 674 void *parent_baton, 675 apr_pool_t *pool) 676{ 677 struct dir_baton *pb = parent_baton; 678 const char *mypath = apr_pstrdup(pb->pool, path); 679 680 /* remember this path needs to be deleted. */ 681 svn_hash_sets(pb->deleted_entries, mypath, pb); 682 683 return SVN_NO_ERROR; 684} 685 686 687static svn_error_t * 688add_directory(const char *path, 689 void *parent_baton, 690 const char *copyfrom_path, 691 svn_revnum_t copyfrom_rev, 692 apr_pool_t *pool, 693 void **child_baton) 694{ 695 struct dir_baton *pb = parent_baton; 696 struct edit_baton *eb = pb->edit_baton; 697 void *val; 698 svn_boolean_t is_copy = FALSE; 699 struct dir_baton *new_db 700 = make_dir_baton(path, copyfrom_path, copyfrom_rev, eb, pb, TRUE, pool); 701 702 /* This might be a replacement -- is the path already deleted? */ 703 val = svn_hash_gets(pb->deleted_entries, path); 704 705 /* Detect an add-with-history. */ 706 is_copy = ARE_VALID_COPY_ARGS(copyfrom_path, copyfrom_rev); 707 708 /* Dump the node. */ 709 SVN_ERR(dump_node(eb, path, 710 svn_node_dir, 711 val ? svn_node_action_replace : svn_node_action_add, 712 is_copy, 713 is_copy ? copyfrom_path : NULL, 714 is_copy ? copyfrom_rev : SVN_INVALID_REVNUM, 715 pool)); 716 717 if (val) 718 /* Delete the path, it's now been dumped. */ 719 svn_hash_sets(pb->deleted_entries, path, NULL); 720 721 new_db->written_out = TRUE; 722 723 *child_baton = new_db; 724 return SVN_NO_ERROR; 725} 726 727 728static svn_error_t * 729open_directory(const char *path, 730 void *parent_baton, 731 svn_revnum_t base_revision, 732 apr_pool_t *pool, 733 void **child_baton) 734{ 735 struct dir_baton *pb = parent_baton; 736 struct edit_baton *eb = pb->edit_baton; 737 struct dir_baton *new_db; 738 const char *cmp_path = NULL; 739 svn_revnum_t cmp_rev = SVN_INVALID_REVNUM; 740 741 /* If the parent directory has explicit comparison path and rev, 742 record the same for this one. */ 743 if (ARE_VALID_COPY_ARGS(pb->cmp_path, pb->cmp_rev)) 744 { 745 cmp_path = svn_relpath_join(pb->cmp_path, 746 svn_relpath_basename(path, pool), pool); 747 cmp_rev = pb->cmp_rev; 748 } 749 750 new_db = make_dir_baton(path, cmp_path, cmp_rev, eb, pb, FALSE, pool); 751 *child_baton = new_db; 752 return SVN_NO_ERROR; 753} 754 755 756static svn_error_t * 757close_directory(void *dir_baton, 758 apr_pool_t *pool) 759{ 760 struct dir_baton *db = dir_baton; 761 struct edit_baton *eb = db->edit_baton; 762 apr_pool_t *subpool = svn_pool_create(pool); 763 int i; 764 apr_array_header_t *sorted_entries; 765 766 /* Sort entries lexically instead of as paths. Even though the entries 767 * are full paths they're all in the same directory (see comment in struct 768 * dir_baton definition). So we really want to sort by basename, in which 769 * case the lexical sort function is more efficient. */ 770 sorted_entries = svn_sort__hash(db->deleted_entries, 771 svn_sort_compare_items_lexically, pool); 772 for (i = 0; i < sorted_entries->nelts; i++) 773 { 774 const char *path = APR_ARRAY_IDX(sorted_entries, i, 775 svn_sort__item_t).key; 776 777 svn_pool_clear(subpool); 778 779 /* By sending 'svn_node_unknown', the Node-kind: header simply won't 780 be written out. No big deal at all, really. The loader 781 shouldn't care. */ 782 SVN_ERR(dump_node(eb, path, 783 svn_node_unknown, svn_node_action_delete, 784 FALSE, NULL, SVN_INVALID_REVNUM, subpool)); 785 } 786 787 svn_pool_destroy(subpool); 788 return SVN_NO_ERROR; 789} 790 791 792static svn_error_t * 793add_file(const char *path, 794 void *parent_baton, 795 const char *copyfrom_path, 796 svn_revnum_t copyfrom_rev, 797 apr_pool_t *pool, 798 void **file_baton) 799{ 800 struct dir_baton *pb = parent_baton; 801 struct edit_baton *eb = pb->edit_baton; 802 void *val; 803 svn_boolean_t is_copy = FALSE; 804 805 /* This might be a replacement -- is the path already deleted? */ 806 val = svn_hash_gets(pb->deleted_entries, path); 807 808 /* Detect add-with-history. */ 809 is_copy = ARE_VALID_COPY_ARGS(copyfrom_path, copyfrom_rev); 810 811 /* Dump the node. */ 812 SVN_ERR(dump_node(eb, path, 813 svn_node_file, 814 val ? svn_node_action_replace : svn_node_action_add, 815 is_copy, 816 is_copy ? copyfrom_path : NULL, 817 is_copy ? copyfrom_rev : SVN_INVALID_REVNUM, 818 pool)); 819 820 if (val) 821 /* delete the path, it's now been dumped. */ 822 svn_hash_sets(pb->deleted_entries, path, NULL); 823 824 *file_baton = NULL; /* muhahahaha */ 825 return SVN_NO_ERROR; 826} 827 828 829static svn_error_t * 830open_file(const char *path, 831 void *parent_baton, 832 svn_revnum_t ancestor_revision, 833 apr_pool_t *pool, 834 void **file_baton) 835{ 836 struct dir_baton *pb = parent_baton; 837 struct edit_baton *eb = pb->edit_baton; 838 const char *cmp_path = NULL; 839 svn_revnum_t cmp_rev = SVN_INVALID_REVNUM; 840 841 /* If the parent directory has explicit comparison path and rev, 842 record the same for this one. */ 843 if (ARE_VALID_COPY_ARGS(pb->cmp_path, pb->cmp_rev)) 844 { 845 cmp_path = svn_relpath_join(pb->cmp_path, 846 svn_relpath_basename(path, pool), pool); 847 cmp_rev = pb->cmp_rev; 848 } 849 850 SVN_ERR(dump_node(eb, path, 851 svn_node_file, svn_node_action_change, 852 FALSE, cmp_path, cmp_rev, pool)); 853 854 *file_baton = NULL; /* muhahahaha again */ 855 return SVN_NO_ERROR; 856} 857 858 859static svn_error_t * 860change_dir_prop(void *parent_baton, 861 const char *name, 862 const svn_string_t *value, 863 apr_pool_t *pool) 864{ 865 struct dir_baton *db = parent_baton; 866 struct edit_baton *eb = db->edit_baton; 867 868 /* This function is what distinguishes between a directory that is 869 opened to merely get somewhere, vs. one that is opened because it 870 *actually* changed by itself. */ 871 if (! db->written_out) 872 { 873 SVN_ERR(dump_node(eb, db->path, 874 svn_node_dir, svn_node_action_change, 875 FALSE, db->cmp_path, db->cmp_rev, pool)); 876 db->written_out = TRUE; 877 } 878 return SVN_NO_ERROR; 879} 880 881static svn_error_t * 882fetch_props_func(apr_hash_t **props, 883 void *baton, 884 const char *path, 885 svn_revnum_t base_revision, 886 apr_pool_t *result_pool, 887 apr_pool_t *scratch_pool) 888{ 889 struct edit_baton *eb = baton; 890 svn_error_t *err; 891 svn_fs_root_t *fs_root; 892 893 if (!SVN_IS_VALID_REVNUM(base_revision)) 894 base_revision = eb->current_rev - 1; 895 896 SVN_ERR(svn_fs_revision_root(&fs_root, eb->fs, base_revision, scratch_pool)); 897 898 err = svn_fs_node_proplist(props, fs_root, path, result_pool); 899 if (err && err->apr_err == SVN_ERR_FS_NOT_FOUND) 900 { 901 svn_error_clear(err); 902 *props = apr_hash_make(result_pool); 903 return SVN_NO_ERROR; 904 } 905 else if (err) 906 return svn_error_trace(err); 907 908 return SVN_NO_ERROR; 909} 910 911static svn_error_t * 912fetch_kind_func(svn_node_kind_t *kind, 913 void *baton, 914 const char *path, 915 svn_revnum_t base_revision, 916 apr_pool_t *scratch_pool) 917{ 918 struct edit_baton *eb = baton; 919 svn_fs_root_t *fs_root; 920 921 if (!SVN_IS_VALID_REVNUM(base_revision)) 922 base_revision = eb->current_rev - 1; 923 924 SVN_ERR(svn_fs_revision_root(&fs_root, eb->fs, base_revision, scratch_pool)); 925 926 SVN_ERR(svn_fs_check_path(kind, fs_root, path, scratch_pool)); 927 928 return SVN_NO_ERROR; 929} 930 931static svn_error_t * 932fetch_base_func(const char **filename, 933 void *baton, 934 const char *path, 935 svn_revnum_t base_revision, 936 apr_pool_t *result_pool, 937 apr_pool_t *scratch_pool) 938{ 939 struct edit_baton *eb = baton; 940 svn_stream_t *contents; 941 svn_stream_t *file_stream; 942 const char *tmp_filename; 943 svn_error_t *err; 944 svn_fs_root_t *fs_root; 945 946 if (!SVN_IS_VALID_REVNUM(base_revision)) 947 base_revision = eb->current_rev - 1; 948 949 SVN_ERR(svn_fs_revision_root(&fs_root, eb->fs, base_revision, scratch_pool)); 950 951 err = svn_fs_file_contents(&contents, fs_root, path, scratch_pool); 952 if (err && err->apr_err == SVN_ERR_FS_NOT_FOUND) 953 { 954 svn_error_clear(err); 955 *filename = NULL; 956 return SVN_NO_ERROR; 957 } 958 else if (err) 959 return svn_error_trace(err); 960 SVN_ERR(svn_stream_open_unique(&file_stream, &tmp_filename, NULL, 961 svn_io_file_del_on_pool_cleanup, 962 scratch_pool, scratch_pool)); 963 SVN_ERR(svn_stream_copy3(contents, file_stream, NULL, NULL, scratch_pool)); 964 965 *filename = apr_pstrdup(result_pool, tmp_filename); 966 967 return SVN_NO_ERROR; 968} 969 970 971static svn_error_t * 972get_dump_editor(const svn_delta_editor_t **editor, 973 void **edit_baton, 974 svn_fs_t *fs, 975 svn_revnum_t to_rev, 976 const char *root_path, 977 svn_stream_t *stream, 978 svn_boolean_t *found_old_reference, 979 svn_boolean_t *found_old_mergeinfo, 980 svn_error_t *(*custom_close_directory)(void *dir_baton, 981 apr_pool_t *scratch_pool), 982 svn_repos_notify_func_t notify_func, 983 void *notify_baton, 984 svn_revnum_t oldest_dumped_rev, 985 svn_boolean_t use_deltas, 986 svn_boolean_t verify, 987 apr_pool_t *pool) 988{ 989 /* Allocate an edit baton to be stored in every directory baton. 990 Set it up for the directory baton we create here, which is the 991 root baton. */ 992 struct edit_baton *eb = apr_pcalloc(pool, sizeof(*eb)); 993 svn_delta_editor_t *dump_editor = svn_delta_default_editor(pool); 994 svn_delta_shim_callbacks_t *shim_callbacks = 995 svn_delta_shim_callbacks_default(pool); 996 997 /* Set up the edit baton. */ 998 eb->stream = stream; 999 eb->notify_func = notify_func; 1000 eb->notify_baton = notify_baton; 1001 eb->oldest_dumped_rev = oldest_dumped_rev; 1002 eb->bufsize = sizeof(eb->buffer); 1003 eb->path = apr_pstrdup(pool, root_path); 1004 SVN_ERR(svn_fs_revision_root(&(eb->fs_root), fs, to_rev, pool)); 1005 eb->fs = fs; 1006 eb->current_rev = to_rev; 1007 eb->use_deltas = use_deltas; 1008 eb->verify = verify; 1009 eb->found_old_reference = found_old_reference; 1010 eb->found_old_mergeinfo = found_old_mergeinfo; 1011 1012 /* Set up the editor. */ 1013 dump_editor->open_root = open_root; 1014 dump_editor->delete_entry = delete_entry; 1015 dump_editor->add_directory = add_directory; 1016 dump_editor->open_directory = open_directory; 1017 if (custom_close_directory) 1018 dump_editor->close_directory = custom_close_directory; 1019 else 1020 dump_editor->close_directory = close_directory; 1021 dump_editor->change_dir_prop = change_dir_prop; 1022 dump_editor->add_file = add_file; 1023 dump_editor->open_file = open_file; 1024 1025 *edit_baton = eb; 1026 *editor = dump_editor; 1027 1028 shim_callbacks->fetch_kind_func = fetch_kind_func; 1029 shim_callbacks->fetch_props_func = fetch_props_func; 1030 shim_callbacks->fetch_base_func = fetch_base_func; 1031 shim_callbacks->fetch_baton = eb; 1032 1033 SVN_ERR(svn_editor__insert_shims(editor, edit_baton, *editor, *edit_baton, 1034 NULL, NULL, shim_callbacks, pool, pool)); 1035 1036 return SVN_NO_ERROR; 1037} 1038 1039/*----------------------------------------------------------------------*/ 1040 1041/** The main dumping routine, svn_repos_dump_fs. **/ 1042 1043 1044/* Helper for svn_repos_dump_fs. 1045 1046 Write a revision record of REV in FS to writable STREAM, using POOL. 1047 */ 1048static svn_error_t * 1049write_revision_record(svn_stream_t *stream, 1050 svn_fs_t *fs, 1051 svn_revnum_t rev, 1052 apr_pool_t *pool) 1053{ 1054 apr_size_t len; 1055 apr_hash_t *props; 1056 svn_stringbuf_t *encoded_prophash; 1057 apr_time_t timetemp; 1058 svn_string_t *datevalue; 1059 svn_stream_t *propstream; 1060 1061 /* Read the revision props even if we're aren't going to dump 1062 them for verification purposes */ 1063 SVN_ERR(svn_fs_revision_proplist(&props, fs, rev, pool)); 1064 1065 /* Run revision date properties through the time conversion to 1066 canonicalize them. */ 1067 /* ### Remove this when it is no longer needed for sure. */ 1068 datevalue = svn_hash_gets(props, SVN_PROP_REVISION_DATE); 1069 if (datevalue) 1070 { 1071 SVN_ERR(svn_time_from_cstring(&timetemp, datevalue->data, pool)); 1072 datevalue = svn_string_create(svn_time_to_cstring(timetemp, pool), 1073 pool); 1074 svn_hash_sets(props, SVN_PROP_REVISION_DATE, datevalue); 1075 } 1076 1077 encoded_prophash = svn_stringbuf_create_ensure(0, pool); 1078 propstream = svn_stream_from_stringbuf(encoded_prophash, pool); 1079 SVN_ERR(svn_hash_write2(props, propstream, "PROPS-END", pool)); 1080 SVN_ERR(svn_stream_close(propstream)); 1081 1082 /* ### someday write a revision-content-checksum */ 1083 1084 SVN_ERR(svn_stream_printf(stream, pool, 1085 SVN_REPOS_DUMPFILE_REVISION_NUMBER 1086 ": %ld\n", rev)); 1087 SVN_ERR(svn_stream_printf(stream, pool, 1088 SVN_REPOS_DUMPFILE_PROP_CONTENT_LENGTH 1089 ": %" APR_SIZE_T_FMT "\n", 1090 encoded_prophash->len)); 1091 1092 /* Write out a regular Content-length header for the benefit of 1093 non-Subversion RFC-822 parsers. */ 1094 SVN_ERR(svn_stream_printf(stream, pool, 1095 SVN_REPOS_DUMPFILE_CONTENT_LENGTH 1096 ": %" APR_SIZE_T_FMT "\n\n", 1097 encoded_prophash->len)); 1098 1099 len = encoded_prophash->len; 1100 SVN_ERR(svn_stream_write(stream, encoded_prophash->data, &len)); 1101 1102 len = 1; 1103 return svn_stream_write(stream, "\n", &len); 1104} 1105 1106 1107 1108/* The main dumper. */ 1109svn_error_t * 1110svn_repos_dump_fs3(svn_repos_t *repos, 1111 svn_stream_t *stream, 1112 svn_revnum_t start_rev, 1113 svn_revnum_t end_rev, 1114 svn_boolean_t incremental, 1115 svn_boolean_t use_deltas, 1116 svn_repos_notify_func_t notify_func, 1117 void *notify_baton, 1118 svn_cancel_func_t cancel_func, 1119 void *cancel_baton, 1120 apr_pool_t *pool) 1121{ 1122 const svn_delta_editor_t *dump_editor; 1123 void *dump_edit_baton = NULL; 1124 svn_revnum_t i; 1125 svn_fs_t *fs = svn_repos_fs(repos); 1126 apr_pool_t *subpool = svn_pool_create(pool); 1127 svn_revnum_t youngest; 1128 const char *uuid; 1129 int version; 1130 svn_boolean_t found_old_reference = FALSE; 1131 svn_boolean_t found_old_mergeinfo = FALSE; 1132 svn_repos_notify_t *notify; 1133 1134 /* Determine the current youngest revision of the filesystem. */ 1135 SVN_ERR(svn_fs_youngest_rev(&youngest, fs, pool)); 1136 1137 /* Use default vals if necessary. */ 1138 if (! SVN_IS_VALID_REVNUM(start_rev)) 1139 start_rev = 0; 1140 if (! SVN_IS_VALID_REVNUM(end_rev)) 1141 end_rev = youngest; 1142 if (! stream) 1143 stream = svn_stream_empty(pool); 1144 1145 /* Validate the revisions. */ 1146 if (start_rev > end_rev) 1147 return svn_error_createf(SVN_ERR_REPOS_BAD_ARGS, NULL, 1148 _("Start revision %ld" 1149 " is greater than end revision %ld"), 1150 start_rev, end_rev); 1151 if (end_rev > youngest) 1152 return svn_error_createf(SVN_ERR_REPOS_BAD_ARGS, NULL, 1153 _("End revision %ld is invalid " 1154 "(youngest revision is %ld)"), 1155 end_rev, youngest); 1156 if ((start_rev == 0) && incremental) 1157 incremental = FALSE; /* revision 0 looks the same regardless of 1158 whether or not this is an incremental 1159 dump, so just simplify things. */ 1160 1161 /* Write out the UUID. */ 1162 SVN_ERR(svn_fs_get_uuid(fs, &uuid, pool)); 1163 1164 /* If we're not using deltas, use the previous version, for 1165 compatibility with svn 1.0.x. */ 1166 version = SVN_REPOS_DUMPFILE_FORMAT_VERSION; 1167 if (!use_deltas) 1168 version--; 1169 1170 /* Write out "general" metadata for the dumpfile. In this case, a 1171 magic header followed by a dumpfile format version. */ 1172 SVN_ERR(svn_stream_printf(stream, pool, 1173 SVN_REPOS_DUMPFILE_MAGIC_HEADER ": %d\n\n", 1174 version)); 1175 SVN_ERR(svn_stream_printf(stream, pool, SVN_REPOS_DUMPFILE_UUID 1176 ": %s\n\n", uuid)); 1177 1178 /* Create a notify object that we can reuse in the loop. */ 1179 if (notify_func) 1180 notify = svn_repos_notify_create(svn_repos_notify_dump_rev_end, 1181 pool); 1182 1183 /* Main loop: we're going to dump revision i. */ 1184 for (i = start_rev; i <= end_rev; i++) 1185 { 1186 svn_revnum_t from_rev, to_rev; 1187 svn_fs_root_t *to_root; 1188 svn_boolean_t use_deltas_for_rev; 1189 1190 svn_pool_clear(subpool); 1191 1192 /* Check for cancellation. */ 1193 if (cancel_func) 1194 SVN_ERR(cancel_func(cancel_baton)); 1195 1196 /* Special-case the initial revision dump: it needs to contain 1197 *all* nodes, because it's the foundation of all future 1198 revisions in the dumpfile. */ 1199 if ((i == start_rev) && (! incremental)) 1200 { 1201 /* Special-special-case a dump of revision 0. */ 1202 if (i == 0) 1203 { 1204 /* Just write out the one revision 0 record and move on. 1205 The parser might want to use its properties. */ 1206 SVN_ERR(write_revision_record(stream, fs, 0, subpool)); 1207 to_rev = 0; 1208 goto loop_end; 1209 } 1210 1211 /* Compare START_REV to revision 0, so that everything 1212 appears to be added. */ 1213 from_rev = 0; 1214 to_rev = i; 1215 } 1216 else 1217 { 1218 /* In the normal case, we want to compare consecutive revs. */ 1219 from_rev = i - 1; 1220 to_rev = i; 1221 } 1222 1223 /* Write the revision record. */ 1224 SVN_ERR(write_revision_record(stream, fs, to_rev, subpool)); 1225 1226 /* Fetch the editor which dumps nodes to a file. Regardless of 1227 what we've been told, don't use deltas for the first rev of a 1228 non-incremental dump. */ 1229 use_deltas_for_rev = use_deltas && (incremental || i != start_rev); 1230 SVN_ERR(get_dump_editor(&dump_editor, &dump_edit_baton, fs, to_rev, 1231 "", stream, &found_old_reference, 1232 &found_old_mergeinfo, NULL, 1233 notify_func, notify_baton, 1234 start_rev, use_deltas_for_rev, FALSE, subpool)); 1235 1236 /* Drive the editor in one way or another. */ 1237 SVN_ERR(svn_fs_revision_root(&to_root, fs, to_rev, subpool)); 1238 1239 /* If this is the first revision of a non-incremental dump, 1240 we're in for a full tree dump. Otherwise, we want to simply 1241 replay the revision. */ 1242 if ((i == start_rev) && (! incremental)) 1243 { 1244 svn_fs_root_t *from_root; 1245 SVN_ERR(svn_fs_revision_root(&from_root, fs, from_rev, subpool)); 1246 SVN_ERR(svn_repos_dir_delta2(from_root, "", "", 1247 to_root, "", 1248 dump_editor, dump_edit_baton, 1249 NULL, 1250 NULL, 1251 FALSE, /* don't send text-deltas */ 1252 svn_depth_infinity, 1253 FALSE, /* don't send entry props */ 1254 FALSE, /* don't ignore ancestry */ 1255 subpool)); 1256 } 1257 else 1258 { 1259 SVN_ERR(svn_repos_replay2(to_root, "", SVN_INVALID_REVNUM, FALSE, 1260 dump_editor, dump_edit_baton, 1261 NULL, NULL, subpool)); 1262 1263 /* While our editor close_edit implementation is a no-op, we still 1264 do this for completeness. */ 1265 SVN_ERR(dump_editor->close_edit(dump_edit_baton, subpool)); 1266 } 1267 1268 loop_end: 1269 if (notify_func) 1270 { 1271 notify->revision = to_rev; 1272 notify_func(notify_baton, notify, subpool); 1273 } 1274 } 1275 1276 if (notify_func) 1277 { 1278 /* Did we issue any warnings about references to revisions older than 1279 the oldest dumped revision? If so, then issue a final generic 1280 warning, since the inline warnings already issued might easily be 1281 missed. */ 1282 1283 notify = svn_repos_notify_create(svn_repos_notify_dump_end, subpool); 1284 notify_func(notify_baton, notify, subpool); 1285 1286 if (found_old_reference) 1287 { 1288 notify = svn_repos_notify_create(svn_repos_notify_warning, subpool); 1289 1290 notify->warning = svn_repos_notify_warning_found_old_reference; 1291 notify->warning_str = _("The range of revisions dumped " 1292 "contained references to " 1293 "copy sources outside that " 1294 "range."); 1295 notify_func(notify_baton, notify, subpool); 1296 } 1297 1298 /* Ditto if we issued any warnings about old revisions referenced 1299 in dumped mergeinfo. */ 1300 if (found_old_mergeinfo) 1301 { 1302 notify = svn_repos_notify_create(svn_repos_notify_warning, subpool); 1303 1304 notify->warning = svn_repos_notify_warning_found_old_mergeinfo; 1305 notify->warning_str = _("The range of revisions dumped " 1306 "contained mergeinfo " 1307 "which reference revisions outside " 1308 "that range."); 1309 notify_func(notify_baton, notify, subpool); 1310 } 1311 } 1312 1313 svn_pool_destroy(subpool); 1314 1315 return SVN_NO_ERROR; 1316} 1317 1318 1319/*----------------------------------------------------------------------*/ 1320 1321/* verify, based on dump */ 1322 1323 1324/* Creating a new revision that changes /A/B/E/bravo means creating new 1325 directory listings for /, /A, /A/B, and /A/B/E in the new revision, with 1326 each entry not changed in the new revision a link back to the entry in a 1327 previous revision. svn_repos_replay()ing a revision does not verify that 1328 those links are correct. 1329 1330 For paths actually changed in the revision we verify, we get directory 1331 contents or file length twice: once in the dump editor, and once here. 1332 We could create a new verify baton, store in it the changed paths, and 1333 skip those here, but that means building an entire wrapper editor and 1334 managing two levels of batons. The impact from checking these entries 1335 twice should be minimal, while the code to avoid it is not. 1336*/ 1337 1338static svn_error_t * 1339verify_directory_entry(void *baton, const void *key, apr_ssize_t klen, 1340 void *val, apr_pool_t *pool) 1341{ 1342 struct dir_baton *db = baton; 1343 svn_fs_dirent_t *dirent = (svn_fs_dirent_t *)val; 1344 char *path = svn_relpath_join(db->path, (const char *)key, pool); 1345 apr_hash_t *dirents; 1346 svn_filesize_t len; 1347 1348 /* since we can't access the directory entries directly by their ID, 1349 we need to navigate from the FS_ROOT to them (relatively expensive 1350 because we may start at a never rev than the last change to node). */ 1351 switch (dirent->kind) { 1352 case svn_node_dir: 1353 /* Getting this directory's contents is enough to ensure that our 1354 link to it is correct. */ 1355 SVN_ERR(svn_fs_dir_entries(&dirents, db->edit_baton->fs_root, path, pool)); 1356 break; 1357 case svn_node_file: 1358 /* Getting this file's size is enough to ensure that our link to it 1359 is correct. */ 1360 SVN_ERR(svn_fs_file_length(&len, db->edit_baton->fs_root, path, pool)); 1361 break; 1362 default: 1363 return svn_error_createf(SVN_ERR_NODE_UNEXPECTED_KIND, NULL, 1364 _("Unexpected node kind %d for '%s'"), 1365 dirent->kind, path); 1366 } 1367 1368 return SVN_NO_ERROR; 1369} 1370 1371static svn_error_t * 1372verify_close_directory(void *dir_baton, 1373 apr_pool_t *pool) 1374{ 1375 struct dir_baton *db = dir_baton; 1376 apr_hash_t *dirents; 1377 SVN_ERR(svn_fs_dir_entries(&dirents, db->edit_baton->fs_root, 1378 db->path, pool)); 1379 SVN_ERR(svn_iter_apr_hash(NULL, dirents, verify_directory_entry, 1380 dir_baton, pool)); 1381 return close_directory(dir_baton, pool); 1382} 1383 1384/* Baton type used for forwarding notifications from FS API to REPOS API. */ 1385struct verify_fs2_notify_func_baton_t 1386{ 1387 /* notification function to call (must not be NULL) */ 1388 svn_repos_notify_func_t notify_func; 1389 1390 /* baton to use for it */ 1391 void *notify_baton; 1392 1393 /* type of notification to send (we will simply plug in the revision) */ 1394 svn_repos_notify_t *notify; 1395}; 1396 1397/* Forward the notification to BATON. */ 1398static void 1399verify_fs2_notify_func(svn_revnum_t revision, 1400 void *baton, 1401 apr_pool_t *pool) 1402{ 1403 struct verify_fs2_notify_func_baton_t *notify_baton = baton; 1404 1405 notify_baton->notify->revision = revision; 1406 notify_baton->notify_func(notify_baton->notify_baton, 1407 notify_baton->notify, pool); 1408} 1409 1410svn_error_t * 1411svn_repos_verify_fs2(svn_repos_t *repos, 1412 svn_revnum_t start_rev, 1413 svn_revnum_t end_rev, 1414 svn_repos_notify_func_t notify_func, 1415 void *notify_baton, 1416 svn_cancel_func_t cancel_func, 1417 void *cancel_baton, 1418 apr_pool_t *pool) 1419{ 1420 svn_fs_t *fs = svn_repos_fs(repos); 1421 svn_revnum_t youngest; 1422 svn_revnum_t rev; 1423 apr_pool_t *iterpool = svn_pool_create(pool); 1424 svn_repos_notify_t *notify; 1425 svn_fs_progress_notify_func_t verify_notify = NULL; 1426 struct verify_fs2_notify_func_baton_t *verify_notify_baton = NULL; 1427 1428 /* Determine the current youngest revision of the filesystem. */ 1429 SVN_ERR(svn_fs_youngest_rev(&youngest, fs, pool)); 1430 1431 /* Use default vals if necessary. */ 1432 if (! SVN_IS_VALID_REVNUM(start_rev)) 1433 start_rev = 0; 1434 if (! SVN_IS_VALID_REVNUM(end_rev)) 1435 end_rev = youngest; 1436 1437 /* Validate the revisions. */ 1438 if (start_rev > end_rev) 1439 return svn_error_createf(SVN_ERR_REPOS_BAD_ARGS, NULL, 1440 _("Start revision %ld" 1441 " is greater than end revision %ld"), 1442 start_rev, end_rev); 1443 if (end_rev > youngest) 1444 return svn_error_createf(SVN_ERR_REPOS_BAD_ARGS, NULL, 1445 _("End revision %ld is invalid " 1446 "(youngest revision is %ld)"), 1447 end_rev, youngest); 1448 1449 /* Create a notify object that we can reuse within the loop and a 1450 forwarding structure for notifications from inside svn_fs_verify(). */ 1451 if (notify_func) 1452 { 1453 notify = svn_repos_notify_create(svn_repos_notify_verify_rev_end, 1454 pool); 1455 1456 verify_notify = verify_fs2_notify_func; 1457 verify_notify_baton = apr_palloc(pool, sizeof(*verify_notify_baton)); 1458 verify_notify_baton->notify_func = notify_func; 1459 verify_notify_baton->notify_baton = notify_baton; 1460 verify_notify_baton->notify 1461 = svn_repos_notify_create(svn_repos_notify_verify_rev_structure, pool); 1462 } 1463 1464 /* Verify global metadata and backend-specific data first. */ 1465 SVN_ERR(svn_fs_verify(svn_fs_path(fs, pool), svn_fs_config(fs, pool), 1466 start_rev, end_rev, 1467 verify_notify, verify_notify_baton, 1468 cancel_func, cancel_baton, pool)); 1469 1470 for (rev = start_rev; rev <= end_rev; rev++) 1471 { 1472 const svn_delta_editor_t *dump_editor; 1473 void *dump_edit_baton; 1474 const svn_delta_editor_t *cancel_editor; 1475 void *cancel_edit_baton; 1476 svn_fs_root_t *to_root; 1477 apr_hash_t *props; 1478 1479 svn_pool_clear(iterpool); 1480 1481 /* Get cancellable dump editor, but with our close_directory handler. */ 1482 SVN_ERR(get_dump_editor(&dump_editor, &dump_edit_baton, 1483 fs, rev, "", 1484 svn_stream_empty(iterpool), 1485 NULL, NULL, 1486 verify_close_directory, 1487 notify_func, notify_baton, 1488 start_rev, 1489 FALSE, TRUE, /* use_deltas, verify */ 1490 iterpool)); 1491 SVN_ERR(svn_delta_get_cancellation_editor(cancel_func, cancel_baton, 1492 dump_editor, dump_edit_baton, 1493 &cancel_editor, 1494 &cancel_edit_baton, 1495 iterpool)); 1496 1497 SVN_ERR(svn_fs_revision_root(&to_root, fs, rev, iterpool)); 1498 SVN_ERR(svn_fs_verify_root(to_root, iterpool)); 1499 1500 SVN_ERR(svn_repos_replay2(to_root, "", SVN_INVALID_REVNUM, FALSE, 1501 cancel_editor, cancel_edit_baton, 1502 NULL, NULL, iterpool)); 1503 /* While our editor close_edit implementation is a no-op, we still 1504 do this for completeness. */ 1505 SVN_ERR(cancel_editor->close_edit(cancel_edit_baton, iterpool)); 1506 1507 SVN_ERR(svn_fs_revision_proplist(&props, fs, rev, iterpool)); 1508 1509 if (notify_func) 1510 { 1511 notify->revision = rev; 1512 notify_func(notify_baton, notify, iterpool); 1513 } 1514 } 1515 1516 /* We're done. */ 1517 if (notify_func) 1518 { 1519 notify = svn_repos_notify_create(svn_repos_notify_verify_end, iterpool); 1520 notify_func(notify_baton, notify, iterpool); 1521 } 1522 1523 /* Per-backend verification. */ 1524 svn_pool_destroy(iterpool); 1525 1526 return SVN_NO_ERROR; 1527} 1528