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