svnrdump.c revision 362181
1/* 2 * svnrdump.c: Produce a dumpfile of a local or remote repository 3 * without touching the filesystem, but for temporary files. 4 * 5 * ==================================================================== 6 * Licensed to the Apache Software Foundation (ASF) under one 7 * or more contributor license agreements. See the NOTICE file 8 * distributed with this work for additional information 9 * regarding copyright ownership. The ASF licenses this file 10 * to you under the Apache License, Version 2.0 (the 11 * "License"); you may not use this file except in compliance 12 * with the License. You may obtain a copy of the License at 13 * 14 * http://www.apache.org/licenses/LICENSE-2.0 15 * 16 * Unless required by applicable law or agreed to in writing, 17 * software distributed under the License is distributed on an 18 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 19 * KIND, either express or implied. See the License for the 20 * specific language governing permissions and limitations 21 * under the License. 22 * ==================================================================== 23 */ 24 25#include <apr_uri.h> 26 27#include "svn_pools.h" 28#include "svn_cmdline.h" 29#include "svn_client.h" 30#include "svn_hash.h" 31#include "svn_ra.h" 32#include "svn_repos.h" 33#include "svn_path.h" 34#include "svn_utf.h" 35#include "svn_private_config.h" 36#include "svn_string.h" 37#include "svn_props.h" 38 39#include "svnrdump.h" 40 41#include "private/svn_repos_private.h" 42#include "private/svn_cmdline_private.h" 43#include "private/svn_ra_private.h" 44 45 46 47/*** Cancellation ***/ 48 49/* Our cancellation callback. */ 50static svn_cancel_func_t check_cancel = NULL; 51 52 53 54static svn_opt_subcommand_t dump_cmd, load_cmd; 55 56enum svn_svnrdump__longopt_t 57 { 58 opt_config_dir = SVN_OPT_FIRST_LONGOPT_ID, 59 opt_config_option, 60 opt_auth_username, 61 opt_auth_password, 62 opt_auth_password_from_stdin, 63 opt_auth_nocache, 64 opt_non_interactive, 65 opt_skip_revprop, 66 opt_force_interactive, 67 opt_incremental, 68 opt_trust_server_cert, 69 opt_trust_server_cert_failures, 70 opt_version 71 }; 72 73#define SVN_SVNRDUMP__BASE_OPTIONS opt_config_dir, \ 74 opt_config_option, \ 75 opt_auth_username, \ 76 opt_auth_password, \ 77 opt_auth_password_from_stdin, \ 78 opt_auth_nocache, \ 79 opt_trust_server_cert, \ 80 opt_trust_server_cert_failures, \ 81 opt_non_interactive, \ 82 opt_force_interactive 83 84static const svn_opt_subcommand_desc3_t svnrdump__cmd_table[] = 85{ 86 { "dump", dump_cmd, { 0 }, {N_( 87 "usage: svnrdump dump URL [-r LOWER[:UPPER]]\n" 88 "\n"), N_( 89 "Dump revisions LOWER to UPPER of repository at remote URL to stdout\n" 90 "in a 'dumpfile' portable format. If only LOWER is given, dump that\n" 91 "one revision.\n" 92 )}, 93 { 'r', 'q', opt_incremental, 'F', SVN_SVNRDUMP__BASE_OPTIONS }, 94 {{'F', N_("write to file ARG instead of stdout")}} }, 95 { "load", load_cmd, { 0 }, {N_( 96 "usage: svnrdump load URL\n" 97 "\n"), N_( 98 "Load a 'dumpfile' given on stdin to a repository at remote URL.\n" 99 )}, 100 { 'q', opt_skip_revprop, 'F', SVN_SVNRDUMP__BASE_OPTIONS }, 101 {{'F', N_("read from file ARG instead of stdin")}} }, 102 { "help", 0, { "?", "h" }, {N_( 103 "usage: svnrdump help [SUBCOMMAND...]\n" 104 "\n"), N_( 105 "Describe the usage of this program or its subcommands.\n" 106 )}, 107 { 0 } }, 108 { NULL, NULL, { 0 }, {NULL}, { 0 } } 109}; 110 111static const apr_getopt_option_t svnrdump__options[] = 112 { 113 {"revision", 'r', 1, 114 N_("specify revision number ARG (or X:Y range)")}, 115 {"quiet", 'q', 0, 116 N_("no progress (only errors) to stderr")}, 117 {"incremental", opt_incremental, 0, 118 N_("dump incrementally")}, 119 {"skip-revprop", opt_skip_revprop, 1, 120 N_("skip revision property ARG (e.g., \"svn:author\")")}, 121 {"config-dir", opt_config_dir, 1, 122 N_("read user configuration files from directory ARG")}, 123 {"username", opt_auth_username, 1, 124 N_("specify a username ARG")}, 125 {"password", opt_auth_password, 1, 126 N_("specify a password ARG")}, 127 {"password-from-stdin", opt_auth_password_from_stdin, 0, 128 N_("read password from stdin")}, 129 {"non-interactive", opt_non_interactive, 0, 130 N_("do no interactive prompting (default is to prompt\n" 131 " " 132 "only if standard input is a terminal device)")}, 133 {"force-interactive", opt_force_interactive, 0, 134 N_("do interactive prompting even if standard input\n" 135 " " 136 "is not a terminal device")}, 137 {"no-auth-cache", opt_auth_nocache, 0, 138 N_("do not cache authentication tokens")}, 139 {"help", 'h', 0, 140 N_("display this help")}, 141 {"version", opt_version, 0, 142 N_("show program version information")}, 143 {"config-option", opt_config_option, 1, 144 N_("set user configuration option in the format:\n" 145 " " 146 " FILE:SECTION:OPTION=[VALUE]\n" 147 " " 148 "For example:\n" 149 " " 150 " servers:global:http-library=serf")}, 151 {"trust-server-cert", opt_trust_server_cert, 0, 152 N_("deprecated; same as\n" 153 " " 154 "--trust-server-cert-failures=unknown-ca")}, 155 {"trust-server-cert-failures", opt_trust_server_cert_failures, 1, 156 N_("with --non-interactive, accept SSL server\n" 157 " " 158 "certificates with failures; ARG is comma-separated\n" 159 " " 160 "list of 'unknown-ca' (Unknown Authority),\n" 161 " " 162 "'cn-mismatch' (Hostname mismatch), 'expired'\n" 163 " " 164 "(Expired certificate), 'not-yet-valid' (Not yet\n" 165 " " 166 "valid certificate) and 'other' (all other not\n" 167 " " 168 "separately classified certificate errors).")}, 169 {"file", 'F', 1, 170 N_("read/write file ARG instead of stdin/stdout")}, 171 {0, 0, 0, 0} 172 }; 173 174/* Baton for the RA replay session. */ 175struct replay_baton { 176 /* A backdoor ra session for fetching information. */ 177 svn_ra_session_t *extra_ra_session; 178 179 /* The output stream */ 180 svn_stream_t *stdout_stream; 181 182 /* Whether to be quiet. */ 183 svn_boolean_t quiet; 184}; 185 186/* Option set */ 187typedef struct opt_baton_t { 188 svn_client_ctx_t *ctx; 189 svn_ra_session_t *session; 190 const char *url; 191 const char *dumpfile; 192 svn_boolean_t help; 193 svn_boolean_t version; 194 svn_opt_revision_t start_revision; 195 svn_opt_revision_t end_revision; 196 svn_boolean_t quiet; 197 svn_boolean_t incremental; 198 apr_hash_t *skip_revprops; 199} opt_baton_t; 200 201/* Print dumpstream-formatted information about REVISION. 202 * Implements the `svn_ra_replay_revstart_callback_t' interface. 203 */ 204static svn_error_t * 205replay_revstart(svn_revnum_t revision, 206 void *replay_baton, 207 const svn_delta_editor_t **editor, 208 void **edit_baton, 209 apr_hash_t *rev_props, 210 apr_pool_t *pool) 211{ 212 struct replay_baton *rb = replay_baton; 213 apr_hash_t *normal_props; 214 215 /* Normalize and dump the revprops */ 216 SVN_ERR(svn_rdump__normalize_props(&normal_props, rev_props, pool)); 217 SVN_ERR(svn_repos__dump_revision_record(rb->stdout_stream, revision, NULL, 218 normal_props, 219 TRUE /*props_section_always*/, 220 pool)); 221 222 SVN_ERR(svn_rdump__get_dump_editor(editor, edit_baton, revision, 223 rb->stdout_stream, rb->extra_ra_session, 224 NULL, check_cancel, NULL, pool)); 225 226 return SVN_NO_ERROR; 227} 228 229/* Print progress information about the dump of REVISION. 230 Implements the `svn_ra_replay_revfinish_callback_t' interface. */ 231static svn_error_t * 232replay_revend(svn_revnum_t revision, 233 void *replay_baton, 234 const svn_delta_editor_t *editor, 235 void *edit_baton, 236 apr_hash_t *rev_props, 237 apr_pool_t *pool) 238{ 239 /* No resources left to free. */ 240 struct replay_baton *rb = replay_baton; 241 242 SVN_ERR(editor->close_edit(edit_baton, pool)); 243 244 if (! rb->quiet) 245 SVN_ERR(svn_cmdline_fprintf(stderr, pool, "* Dumped revision %lu.\n", 246 revision)); 247 return SVN_NO_ERROR; 248} 249 250#ifdef USE_EV2_IMPL 251/* Print dumpstream-formatted information about REVISION. 252 * Implements the `svn_ra_replay_revstart_callback_t' interface. 253 */ 254static svn_error_t * 255replay_revstart_v2(svn_revnum_t revision, 256 void *replay_baton, 257 svn_editor_t **editor, 258 apr_hash_t *rev_props, 259 apr_pool_t *pool) 260{ 261 struct replay_baton *rb = replay_baton; 262 apr_hash_t *normal_props; 263 264 /* Normalize and dump the revprops */ 265 SVN_ERR(svn_rdump__normalize_props(&normal_props, rev_props, pool)); 266 SVN_ERR(svn_repos__dump_revision_record(rb->stdout_stream, revision, 267 normal_props, 268 TRUE /*props_section_always*/, 269 pool)); 270 271 SVN_ERR(svn_rdump__get_dump_editor_v2(editor, revision, 272 rb->stdout_stream, 273 rb->extra_ra_session, 274 NULL, check_cancel, NULL, pool, pool)); 275 276 return SVN_NO_ERROR; 277} 278 279/* Print progress information about the dump of REVISION. 280 Implements the `svn_ra_replay_revfinish_callback_t' interface. */ 281static svn_error_t * 282replay_revend_v2(svn_revnum_t revision, 283 void *replay_baton, 284 svn_editor_t *editor, 285 apr_hash_t *rev_props, 286 apr_pool_t *pool) 287{ 288 /* No resources left to free. */ 289 struct replay_baton *rb = replay_baton; 290 291 SVN_ERR(svn_editor_complete(editor)); 292 293 if (! rb->quiet) 294 SVN_ERR(svn_cmdline_fprintf(stderr, pool, "* Dumped revision %lu.\n", 295 revision)); 296 return SVN_NO_ERROR; 297} 298#endif 299 300/* Initialize the RA layer, and set *CTX to a new client context baton 301 * allocated from POOL. Use CONFIG_DIR and pass USERNAME, PASSWORD, 302 * CONFIG_DIR and NO_AUTH_CACHE to initialize the authorization baton. 303 * CONFIG_OPTIONS (if not NULL) is a list of configuration overrides. 304 * REPOS_URL is used to fiddle with server-specific configuration 305 * options. 306 */ 307static svn_error_t * 308init_client_context(svn_client_ctx_t **ctx_p, 309 svn_boolean_t non_interactive, 310 const char *username, 311 const char *password, 312 const char *config_dir, 313 const char *repos_url, 314 svn_boolean_t no_auth_cache, 315 svn_boolean_t trust_unknown_ca, 316 svn_boolean_t trust_cn_mismatch, 317 svn_boolean_t trust_expired, 318 svn_boolean_t trust_not_yet_valid, 319 svn_boolean_t trust_other_failure, 320 apr_array_header_t *config_options, 321 apr_pool_t *pool) 322{ 323 svn_client_ctx_t *ctx = NULL; 324 svn_config_t *cfg_config, *cfg_servers; 325 326 SVN_ERR(svn_ra_initialize(pool)); 327 328 SVN_ERR(svn_config_ensure(config_dir, pool)); 329 SVN_ERR(svn_client_create_context2(&ctx, NULL, pool)); 330 331 SVN_ERR(svn_config_get_config(&(ctx->config), config_dir, pool)); 332 333 if (config_options) 334 SVN_ERR(svn_cmdline__apply_config_options(ctx->config, config_options, 335 "svnrdump: ", "--config-option")); 336 337 cfg_config = svn_hash_gets(ctx->config, SVN_CONFIG_CATEGORY_CONFIG); 338 339 /* ### FIXME: This is a hack to work around the fact that our dump 340 ### editor simply can't handle the way ra_serf violates the 341 ### editor v1 drive ordering requirements. 342 ### 343 ### We'll override both the global value and server-specific one 344 ### for the 'http-bulk-updates' and 'http-max-connections' 345 ### options in order to get ra_serf to try a bulk-update if the 346 ### server will allow it, or at least try to limit all its 347 ### auxiliary GETs/PROPFINDs to happening (well-ordered) on a 348 ### single server connection. 349 ### 350 ### See https://issues.apache.org/jira/browse/SVN-4116. 351 */ 352 cfg_servers = svn_hash_gets(ctx->config, SVN_CONFIG_CATEGORY_SERVERS); 353 svn_config_set_bool(cfg_servers, SVN_CONFIG_SECTION_GLOBAL, 354 SVN_CONFIG_OPTION_HTTP_BULK_UPDATES, TRUE); 355 svn_config_set_int64(cfg_servers, SVN_CONFIG_SECTION_GLOBAL, 356 SVN_CONFIG_OPTION_HTTP_MAX_CONNECTIONS, 2); 357 if (cfg_servers) 358 { 359 apr_status_t status; 360 apr_uri_t parsed_url; 361 362 status = apr_uri_parse(pool, repos_url, &parsed_url); 363 if (! status) 364 { 365 const char *server_group; 366 367 server_group = svn_config_find_group(cfg_servers, parsed_url.hostname, 368 SVN_CONFIG_SECTION_GROUPS, pool); 369 if (server_group) 370 { 371 svn_config_set_bool(cfg_servers, server_group, 372 SVN_CONFIG_OPTION_HTTP_BULK_UPDATES, TRUE); 373 svn_config_set_int64(cfg_servers, server_group, 374 SVN_CONFIG_OPTION_HTTP_MAX_CONNECTIONS, 2); 375 } 376 } 377 } 378 379 /* Set up our cancellation support. */ 380 ctx->cancel_func = check_cancel; 381 382 /* Default authentication providers for non-interactive use */ 383 SVN_ERR(svn_cmdline_create_auth_baton2(&(ctx->auth_baton), non_interactive, 384 username, password, config_dir, 385 no_auth_cache, trust_unknown_ca, 386 trust_cn_mismatch, trust_expired, 387 trust_not_yet_valid, 388 trust_other_failure, 389 cfg_config, ctx->cancel_func, 390 ctx->cancel_baton, pool)); 391 *ctx_p = ctx; 392 return SVN_NO_ERROR; 393} 394 395/* Print a revision record header for REVISION to STDOUT_STREAM. Use 396 * SESSION to contact the repository for revision properties and 397 * such. 398 */ 399static svn_error_t * 400dump_revision_header(svn_ra_session_t *session, 401 svn_stream_t *stdout_stream, 402 svn_revnum_t revision, 403 apr_pool_t *pool) 404{ 405 apr_hash_t *prophash; 406 407 SVN_ERR(svn_ra_rev_proplist(session, revision, &prophash, pool)); 408 SVN_ERR(svn_repos__dump_revision_record(stdout_stream, revision, NULL, 409 prophash, 410 TRUE /*props_section_always*/, 411 pool)); 412 return SVN_NO_ERROR; 413} 414 415static svn_error_t * 416dump_initial_full_revision(svn_ra_session_t *session, 417 svn_ra_session_t *extra_ra_session, 418 svn_stream_t *stdout_stream, 419 svn_revnum_t revision, 420 svn_boolean_t quiet, 421 apr_pool_t *pool) 422{ 423 const svn_ra_reporter3_t *reporter; 424 void *report_baton; 425 const svn_delta_editor_t *dump_editor; 426 void *dump_baton; 427 const char *session_url, *source_relpath; 428 429 /* Determine whether we're dumping the repository root URL or some 430 child thereof. If we're dumping a subtree of the repository 431 rather than the root, we have to jump through some hoops to make 432 our update-driven dump generation work the way a replay-driven 433 one would. 434 435 See https://issues.apache.org/jira/browse/SVN-4101 436 */ 437 SVN_ERR(svn_ra_get_session_url(session, &session_url, pool)); 438 SVN_ERR(svn_ra_get_path_relative_to_root(session, &source_relpath, 439 session_url, pool)); 440 441 /* Start with a revision record header. */ 442 SVN_ERR(dump_revision_header(session, stdout_stream, revision, pool)); 443 444 /* Then, we'll drive the dump editor with what would look like a 445 full checkout of the repository as it looked in START_REVISION. 446 We do this by manufacturing a basic 'report' to the update 447 reporter, telling it that we have nothing to start with. The 448 delta between nothing and everything-at-REV is, effectively, a 449 full dump of REV. */ 450 SVN_ERR(svn_rdump__get_dump_editor(&dump_editor, &dump_baton, revision, 451 stdout_stream, extra_ra_session, 452 source_relpath, check_cancel, NULL, pool)); 453 SVN_ERR(svn_ra_do_update3(session, &reporter, &report_baton, revision, 454 "", svn_depth_infinity, FALSE, FALSE, 455 dump_editor, dump_baton, pool, pool)); 456 SVN_ERR(reporter->set_path(report_baton, "", revision, 457 svn_depth_infinity, TRUE, NULL, pool)); 458 SVN_ERR(reporter->finish_report(report_baton, pool)); 459 460 /* All finished with START_REVISION! */ 461 if (! quiet) 462 SVN_ERR(svn_cmdline_fprintf(stderr, pool, "* Dumped revision %lu.\n", 463 revision)); 464 465 return SVN_NO_ERROR; 466} 467 468/* Replay revisions START_REVISION thru END_REVISION (inclusive) of 469 * the repository URL at which SESSION is rooted, using callbacks 470 * which generate Subversion repository dumpstreams describing the 471 * changes made in those revisions. If QUIET is set, don't generate 472 * progress messages. 473 */ 474static svn_error_t * 475replay_revisions(svn_ra_session_t *session, 476 svn_ra_session_t *extra_ra_session, 477 svn_revnum_t start_revision, 478 svn_revnum_t end_revision, 479 svn_boolean_t quiet, 480 svn_boolean_t incremental, 481 const char *dumpfile, 482 apr_pool_t *pool) 483{ 484 struct replay_baton *replay_baton; 485 const char *uuid; 486 svn_stream_t *output_stream; 487 488 if (dumpfile) 489 { 490 SVN_ERR(svn_stream_open_writable(&output_stream, dumpfile, pool, pool)); 491 } 492 else 493 { 494 SVN_ERR(svn_stream_for_stdout(&output_stream, pool)); 495 } 496 497 replay_baton = apr_pcalloc(pool, sizeof(*replay_baton)); 498 replay_baton->stdout_stream = output_stream; 499 replay_baton->extra_ra_session = extra_ra_session; 500 replay_baton->quiet = quiet; 501 502 /* Write the magic header and UUID */ 503 SVN_ERR(svn_repos__dump_magic_header_record(output_stream, 504 SVN_REPOS_DUMPFILE_FORMAT_VERSION, 505 pool)); 506 SVN_ERR(svn_ra_get_uuid2(session, &uuid, pool)); 507 SVN_ERR(svn_repos__dump_uuid_header_record(output_stream, uuid, pool)); 508 509 /* Fake revision 0 if necessary */ 510 if (start_revision == 0) 511 { 512 SVN_ERR(dump_revision_header(session, output_stream, 513 start_revision, pool)); 514 515 /* Revision 0 has no tree changes, so we're done. */ 516 if (! quiet) 517 SVN_ERR(svn_cmdline_fprintf(stderr, pool, "* Dumped revision %lu.\n", 518 start_revision)); 519 start_revision++; 520 521 /* If our first revision is 0, we can treat this as an 522 incremental dump. */ 523 incremental = TRUE; 524 } 525 526 /* If what remains to be dumped is not going to be dumped 527 incrementally, then dump the first revision in full. */ 528 if (!incremental) 529 { 530 SVN_ERR(dump_initial_full_revision(session, extra_ra_session, 531 output_stream, start_revision, 532 quiet, pool)); 533 start_revision++; 534 } 535 536 /* If there are still revisions left to be dumped, do so. */ 537 if (start_revision <= end_revision) 538 { 539#ifndef USE_EV2_IMPL 540 SVN_ERR(svn_ra_replay_range(session, start_revision, end_revision, 541 0, TRUE, replay_revstart, replay_revend, 542 replay_baton, pool)); 543#else 544 SVN_ERR(svn_ra__replay_range_ev2(session, start_revision, end_revision, 545 0, TRUE, replay_revstart_v2, 546 replay_revend_v2, replay_baton, 547 NULL, NULL, NULL, NULL, pool)); 548#endif 549 } 550 551 SVN_ERR(svn_stream_close(output_stream)); 552 return SVN_NO_ERROR; 553} 554 555/* Read a dumpstream from stdin, and use it to feed a loader capable 556 * of transmitting that information to the repository located at URL 557 * (to which SESSION has been opened). AUX_SESSION is a second RA 558 * session opened to the same URL for performing auxiliary out-of-band 559 * operations. 560 */ 561static svn_error_t * 562load_revisions(svn_ra_session_t *session, 563 svn_ra_session_t *aux_session, 564 const char *dumpfile, 565 svn_boolean_t quiet, 566 apr_hash_t *skip_revprops, 567 apr_pool_t *pool) 568{ 569 svn_stream_t *output_stream; 570 571 if (dumpfile) 572 { 573 SVN_ERR(svn_stream_open_readonly(&output_stream, dumpfile, pool, pool)); 574 } 575 else 576 { 577 SVN_ERR(svn_stream_for_stdin2(&output_stream, TRUE, pool)); 578 } 579 580 SVN_ERR(svn_rdump__load_dumpstream(output_stream, session, aux_session, 581 quiet, skip_revprops, 582 check_cancel, NULL, pool)); 583 584 SVN_ERR(svn_stream_close(output_stream)); 585 return SVN_NO_ERROR; 586} 587 588/* Return a program name for this program, the basename of the path 589 * represented by PROGNAME if not NULL; use "svnrdump" otherwise. 590 */ 591static const char * 592ensure_appname(const char *progname, 593 apr_pool_t *pool) 594{ 595 if (!progname) 596 return "svnrdump"; 597 598 return svn_dirent_basename(svn_dirent_internal_style(progname, pool), NULL); 599} 600 601/* Print a simple usage string. */ 602static svn_error_t * 603usage(const char *progname, 604 apr_pool_t *pool) 605{ 606 return svn_cmdline_fprintf(stderr, pool, 607 _("Type '%s help' for usage.\n"), 608 ensure_appname(progname, pool)); 609} 610 611/* Print information about the version of this program and dependent 612 * modules. 613 */ 614static svn_error_t * 615version(const char *progname, 616 svn_boolean_t quiet, 617 apr_pool_t *pool) 618{ 619 svn_stringbuf_t *version_footer = 620 svn_stringbuf_create(_("The following repository access (RA) modules " 621 "are available:\n\n"), 622 pool); 623 624 SVN_ERR(svn_ra_print_modules(version_footer, pool)); 625 return svn_opt_print_help5(NULL, ensure_appname(progname, pool), 626 TRUE, quiet, FALSE, version_footer->data, 627 NULL, NULL, NULL, NULL, NULL, pool); 628} 629 630 631/* Handle the "dump" subcommand. Implements `svn_opt_subcommand_t'. */ 632static svn_error_t * 633dump_cmd(apr_getopt_t *os, 634 void *baton, 635 apr_pool_t *pool) 636{ 637 opt_baton_t *opt_baton = baton; 638 svn_ra_session_t *extra_ra_session; 639 const char *repos_root; 640 641 SVN_ERR(svn_client_open_ra_session2(&extra_ra_session, 642 opt_baton->url, NULL, 643 opt_baton->ctx, pool, pool)); 644 SVN_ERR(svn_ra_get_repos_root2(extra_ra_session, &repos_root, pool)); 645 SVN_ERR(svn_ra_reparent(extra_ra_session, repos_root, pool)); 646 647 return replay_revisions(opt_baton->session, extra_ra_session, 648 opt_baton->start_revision.value.number, 649 opt_baton->end_revision.value.number, 650 opt_baton->quiet, opt_baton->incremental, 651 opt_baton->dumpfile, pool); 652} 653 654/* Handle the "load" subcommand. Implements `svn_opt_subcommand_t'. */ 655static svn_error_t * 656load_cmd(apr_getopt_t *os, 657 void *baton, 658 apr_pool_t *pool) 659{ 660 opt_baton_t *opt_baton = baton; 661 svn_ra_session_t *aux_session; 662 663 SVN_ERR(svn_client_open_ra_session2(&aux_session, opt_baton->url, NULL, 664 opt_baton->ctx, pool, pool)); 665 return load_revisions(opt_baton->session, aux_session, 666 opt_baton->dumpfile, opt_baton->quiet, 667 opt_baton->skip_revprops, pool); 668} 669 670/* Handle the "help" subcommand. Implements `svn_opt_subcommand_t'. */ 671static svn_error_t * 672help_cmd(apr_getopt_t *os, 673 void *baton, 674 apr_pool_t *pool) 675{ 676 const char *header = 677 _("general usage: svnrdump SUBCOMMAND URL [-r LOWER[:UPPER]]\n" 678 "Subversion remote repository dump and load tool.\n" 679 "Type 'svnrdump help <subcommand>' for help on a specific subcommand.\n" 680 "Type 'svnrdump --version' to see the program version and RA modules.\n" 681 "\n" 682 "Available subcommands:\n"); 683 684 return svn_opt_print_help5(os, "svnrdump", FALSE, FALSE, FALSE, NULL, 685 header, svnrdump__cmd_table, svnrdump__options, 686 NULL, NULL, pool); 687} 688 689/* Examine the OPT_BATON's 'start_revision' and 'end_revision' 690 * members, making sure that they make sense (in general, and as 691 * applied to a repository whose current youngest revision is 692 * LATEST_REVISION). 693 */ 694static svn_error_t * 695validate_and_resolve_revisions(opt_baton_t *opt_baton, 696 svn_revnum_t latest_revision, 697 apr_pool_t *pool) 698{ 699 svn_revnum_t provided_start_rev = SVN_INVALID_REVNUM; 700 701 /* Ensure that the start revision is something we can handle. We 702 want a number >= 0. If unspecified, make it a number (r0) -- 703 anything else is bogus. */ 704 if (opt_baton->start_revision.kind == svn_opt_revision_number) 705 { 706 provided_start_rev = opt_baton->start_revision.value.number; 707 } 708 else if (opt_baton->start_revision.kind == svn_opt_revision_head) 709 { 710 opt_baton->start_revision.kind = svn_opt_revision_number; 711 opt_baton->start_revision.value.number = latest_revision; 712 } 713 else if (opt_baton->start_revision.kind == svn_opt_revision_unspecified) 714 { 715 opt_baton->start_revision.kind = svn_opt_revision_number; 716 opt_baton->start_revision.value.number = 0; 717 } 718 719 if (opt_baton->start_revision.kind != svn_opt_revision_number) 720 { 721 return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL, 722 _("Unsupported revision specifier used; use " 723 "only integer values or 'HEAD'")); 724 } 725 726 if ((opt_baton->start_revision.value.number < 0) || 727 (opt_baton->start_revision.value.number > latest_revision)) 728 { 729 return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL, 730 _("Revision '%ld' does not exist"), 731 opt_baton->start_revision.value.number); 732 } 733 734 /* Ensure that the end revision is something we can handle. We want 735 a number <= the youngest, and > the start revision. If 736 unspecified, make it a number (start_revision + 1 if that was 737 specified, the youngest revision in the repository otherwise) -- 738 anything else is bogus. */ 739 if (opt_baton->end_revision.kind == svn_opt_revision_unspecified) 740 { 741 opt_baton->end_revision.kind = svn_opt_revision_number; 742 if (SVN_IS_VALID_REVNUM(provided_start_rev)) 743 opt_baton->end_revision.value.number = provided_start_rev; 744 else 745 opt_baton->end_revision.value.number = latest_revision; 746 } 747 else if (opt_baton->end_revision.kind == svn_opt_revision_head) 748 { 749 opt_baton->end_revision.kind = svn_opt_revision_number; 750 opt_baton->end_revision.value.number = latest_revision; 751 } 752 753 if (opt_baton->end_revision.kind != svn_opt_revision_number) 754 { 755 return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL, 756 _("Unsupported revision specifier used; use " 757 "only integer values or 'HEAD'")); 758 } 759 760 if ((opt_baton->end_revision.value.number < 0) || 761 (opt_baton->end_revision.value.number > latest_revision)) 762 { 763 return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL, 764 _("Revision '%ld' does not exist"), 765 opt_baton->end_revision.value.number); 766 } 767 768 /* Finally, make sure that the end revision is younger than the 769 start revision. We don't do "backwards" 'round here. */ 770 if (opt_baton->end_revision.value.number < 771 opt_baton->start_revision.value.number) 772 { 773 return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL, 774 _("LOWER revision cannot be greater than " 775 "UPPER revision; consider reversing your " 776 "revision range")); 777 } 778 return SVN_NO_ERROR; 779} 780 781/* 782 * On success, leave *EXIT_CODE untouched and return SVN_NO_ERROR. On error, 783 * either return an error to be displayed, or set *EXIT_CODE to non-zero and 784 * return SVN_NO_ERROR. 785 */ 786static svn_error_t * 787sub_main(int *exit_code, int argc, const char *argv[], apr_pool_t *pool) 788{ 789 svn_error_t *err = SVN_NO_ERROR; 790 const svn_opt_subcommand_desc3_t *subcommand = NULL; 791 opt_baton_t *opt_baton; 792 svn_revnum_t latest_revision = SVN_INVALID_REVNUM; 793 const char *config_dir = NULL; 794 const char *username = NULL; 795 const char *password = NULL; 796 svn_boolean_t no_auth_cache = FALSE; 797 svn_boolean_t trust_unknown_ca = FALSE; 798 svn_boolean_t trust_cn_mismatch = FALSE; 799 svn_boolean_t trust_expired = FALSE; 800 svn_boolean_t trust_not_yet_valid = FALSE; 801 svn_boolean_t trust_other_failure = FALSE; 802 svn_boolean_t non_interactive = FALSE; 803 svn_boolean_t force_interactive = FALSE; 804 apr_array_header_t *config_options = NULL; 805 apr_getopt_t *os; 806 apr_array_header_t *received_opts; 807 int i; 808 svn_boolean_t read_pass_from_stdin = FALSE; 809 810 opt_baton = apr_pcalloc(pool, sizeof(*opt_baton)); 811 opt_baton->start_revision.kind = svn_opt_revision_unspecified; 812 opt_baton->end_revision.kind = svn_opt_revision_unspecified; 813 opt_baton->url = NULL; 814 opt_baton->skip_revprops = apr_hash_make(pool); 815 opt_baton->dumpfile = NULL; 816 817 SVN_ERR(svn_cmdline__getopt_init(&os, argc, argv, pool)); 818 819 os->interleave = TRUE; /* Options and arguments can be interleaved */ 820 821 /* Set up our cancellation support. */ 822 check_cancel = svn_cmdline__setup_cancellation_handler(); 823 824 received_opts = apr_array_make(pool, SVN_OPT_MAX_OPTIONS, sizeof(int)); 825 826 while (1) 827 { 828 int opt; 829 const char *opt_arg; 830 apr_status_t status = apr_getopt_long(os, svnrdump__options, &opt, 831 &opt_arg); 832 833 if (APR_STATUS_IS_EOF(status)) 834 break; 835 if (status != APR_SUCCESS) 836 { 837 SVN_ERR(usage(argv[0], pool)); 838 *exit_code = EXIT_FAILURE; 839 return SVN_NO_ERROR; 840 } 841 842 /* Stash the option code in an array before parsing it. */ 843 APR_ARRAY_PUSH(received_opts, int) = opt; 844 845 switch(opt) 846 { 847 case 'r': 848 { 849 /* Make sure we've not seen -r already. */ 850 if (opt_baton->start_revision.kind != svn_opt_revision_unspecified) 851 { 852 return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL, 853 _("Multiple revision arguments " 854 "encountered; try '-r N:M' instead " 855 "of '-r N -r M'")); 856 } 857 /* Parse the -r argument. */ 858 if (svn_opt_parse_revision(&(opt_baton->start_revision), 859 &(opt_baton->end_revision), 860 opt_arg, pool) != 0) 861 { 862 const char *utf8_opt_arg; 863 SVN_ERR(svn_utf_cstring_to_utf8(&utf8_opt_arg, opt_arg, pool)); 864 return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL, 865 _("Syntax error in revision " 866 "argument '%s'"), utf8_opt_arg); 867 } 868 } 869 break; 870 case 'q': 871 opt_baton->quiet = TRUE; 872 break; 873 case opt_config_dir: 874 config_dir = opt_arg; 875 break; 876 case opt_version: 877 opt_baton->version = TRUE; 878 break; 879 case 'h': 880 opt_baton->help = TRUE; 881 break; 882 case opt_auth_username: 883 SVN_ERR(svn_utf_cstring_to_utf8(&username, opt_arg, pool)); 884 break; 885 case opt_auth_password: 886 SVN_ERR(svn_utf_cstring_to_utf8(&password, opt_arg, pool)); 887 break; 888 case opt_auth_password_from_stdin: 889 read_pass_from_stdin = TRUE; 890 break; 891 case opt_auth_nocache: 892 no_auth_cache = TRUE; 893 break; 894 case opt_non_interactive: 895 non_interactive = TRUE; 896 break; 897 case opt_force_interactive: 898 force_interactive = TRUE; 899 break; 900 case opt_incremental: 901 opt_baton->incremental = TRUE; 902 break; 903 case opt_skip_revprop: 904 SVN_ERR(svn_utf_cstring_to_utf8(&opt_arg, opt_arg, pool)); 905 svn_hash_sets(opt_baton->skip_revprops, opt_arg, opt_arg); 906 break; 907 case opt_trust_server_cert: /* backward compat */ 908 trust_unknown_ca = TRUE; 909 break; 910 case opt_trust_server_cert_failures: 911 SVN_ERR(svn_utf_cstring_to_utf8(&opt_arg, opt_arg, pool)); 912 SVN_ERR(svn_cmdline__parse_trust_options( 913 &trust_unknown_ca, 914 &trust_cn_mismatch, 915 &trust_expired, 916 &trust_not_yet_valid, 917 &trust_other_failure, 918 opt_arg, pool)); 919 break; 920 case opt_config_option: 921 if (!config_options) 922 config_options = 923 apr_array_make(pool, 1, 924 sizeof(svn_cmdline__config_argument_t*)); 925 926 SVN_ERR(svn_utf_cstring_to_utf8(&opt_arg, opt_arg, pool)); 927 SVN_ERR(svn_cmdline__parse_config_option(config_options, 928 opt_arg, 929 "svnrdump: ", 930 pool)); 931 break; 932 case 'F': 933 SVN_ERR(svn_utf_cstring_to_utf8(&opt_arg, opt_arg, pool)); 934 opt_baton->dumpfile = opt_arg; 935 break; 936 } 937 } 938 939 /* The --non-interactive and --force-interactive options are mutually 940 * exclusive. */ 941 if (non_interactive && force_interactive) 942 { 943 return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL, 944 _("--non-interactive and --force-interactive " 945 "are mutually exclusive")); 946 } 947 948 if (opt_baton->help) 949 { 950 subcommand = svn_opt_get_canonical_subcommand3(svnrdump__cmd_table, 951 "help"); 952 } 953 if (subcommand == NULL) 954 { 955 if (os->ind >= os->argc) 956 { 957 if (opt_baton->version) 958 { 959 /* Use the "help" subcommand to handle the "--version" option. */ 960 static const svn_opt_subcommand_desc3_t pseudo_cmd = 961 { "--version", help_cmd, {0}, {""}, 962 {opt_version, /* must accept its own option */ 963 'q', /* --quiet */ 964 } }; 965 subcommand = &pseudo_cmd; 966 } 967 968 else 969 { 970 SVN_ERR(help_cmd(NULL, NULL, pool)); 971 *exit_code = EXIT_FAILURE; 972 return SVN_NO_ERROR; 973 } 974 } 975 else 976 { 977 const char *first_arg; 978 979 SVN_ERR(svn_utf_cstring_to_utf8(&first_arg, os->argv[os->ind++], 980 pool)); 981 subcommand = svn_opt_get_canonical_subcommand3(svnrdump__cmd_table, 982 first_arg); 983 984 if (subcommand == NULL) 985 { 986 svn_error_clear( 987 svn_cmdline_fprintf(stderr, pool, 988 _("Unknown subcommand: '%s'\n"), 989 first_arg)); 990 SVN_ERR(help_cmd(NULL, NULL, pool)); 991 *exit_code = EXIT_FAILURE; 992 return SVN_NO_ERROR; 993 } 994 } 995 } 996 997 /* Check that the subcommand wasn't passed any inappropriate options. */ 998 for (i = 0; i < received_opts->nelts; i++) 999 { 1000 int opt_id = APR_ARRAY_IDX(received_opts, i, int); 1001 1002 /* All commands implicitly accept --help, so just skip over this 1003 when we see it. Note that we don't want to include this option 1004 in their "accepted options" list because it would be awfully 1005 redundant to display it in every commands' help text. */ 1006 if (opt_id == 'h' || opt_id == '?') 1007 continue; 1008 1009 if (! svn_opt_subcommand_takes_option4(subcommand, opt_id, NULL)) 1010 { 1011 const char *optstr; 1012 const apr_getopt_option_t *badopt = 1013 svn_opt_get_option_from_code3(opt_id, svnrdump__options, 1014 subcommand, pool); 1015 svn_opt_format_option(&optstr, badopt, FALSE, pool); 1016 if (subcommand->name[0] == '-') 1017 SVN_ERR(help_cmd(NULL, NULL, pool)); 1018 else 1019 svn_error_clear(svn_cmdline_fprintf( 1020 stderr, pool, 1021 _("Subcommand '%s' doesn't accept option '%s'\n" 1022 "Type 'svnrdump help %s' for usage.\n"), 1023 subcommand->name, optstr, subcommand->name)); 1024 *exit_code = EXIT_FAILURE; 1025 return SVN_NO_ERROR; 1026 } 1027 } 1028 1029 if (strcmp(subcommand->name, "--version") == 0) 1030 { 1031 SVN_ERR(version(argv[0], opt_baton->quiet, pool)); 1032 return SVN_NO_ERROR; 1033 } 1034 1035 if (strcmp(subcommand->name, "help") == 0) 1036 { 1037 SVN_ERR(help_cmd(os, opt_baton, pool)); 1038 return SVN_NO_ERROR; 1039 } 1040 1041 /* --trust-* can only be used with --non-interactive */ 1042 if (!non_interactive) 1043 { 1044 if (trust_unknown_ca || trust_cn_mismatch || trust_expired 1045 || trust_not_yet_valid || trust_other_failure) 1046 return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL, 1047 _("--trust-server-cert-failures requires " 1048 "--non-interactive")); 1049 } 1050 1051 if (read_pass_from_stdin && !non_interactive) 1052 { 1053 return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL, 1054 _("--password-from-stdin requires " 1055 "--non-interactive")); 1056 } 1057 1058 if (strcmp(subcommand->name, "load") == 0) 1059 { 1060 if (read_pass_from_stdin && opt_baton->dumpfile == NULL) 1061 { 1062 /* error here, since load cannot process a password over stdin */ 1063 return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL, 1064 _("load subcommand with " 1065 "--password-from-stdin requires -F")); 1066 } 1067 } 1068 1069 /* Expect one more non-option argument: the repository URL. */ 1070 if (os->ind != os->argc - 1) 1071 { 1072 SVN_ERR(usage(argv[0], pool)); 1073 *exit_code = EXIT_FAILURE; 1074 return SVN_NO_ERROR; 1075 } 1076 else 1077 { 1078 const char *repos_url; 1079 1080 SVN_ERR(svn_utf_cstring_to_utf8(&repos_url, os->argv[os->ind], pool)); 1081 if (! svn_path_is_url(repos_url)) 1082 { 1083 return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, 0, 1084 "Target '%s' is not a URL", 1085 repos_url); 1086 } 1087 opt_baton->url = svn_uri_canonicalize(repos_url, pool); 1088 } 1089 1090 if (strcmp(subcommand->name, "load") == 0) 1091 { 1092 /* 1093 * By default (no --*-interactive options given), the 'load' subcommand 1094 * is interactive unless username and password were provided on the 1095 * command line. This allows prompting for auth creds to work without 1096 * requiring users to remember to use --force-interactive. 1097 * See issue #3913, "svnrdump load is not working in interactive mode". 1098 */ 1099 if (!non_interactive && !force_interactive) 1100 force_interactive = (username == NULL || password == NULL); 1101 } 1102 1103 /* Get password from stdin if necessary */ 1104 if (read_pass_from_stdin) 1105 { 1106 SVN_ERR(svn_cmdline__stdin_readline(&password, pool, pool)); 1107 } 1108 1109 non_interactive = !svn_cmdline__be_interactive(non_interactive, 1110 force_interactive); 1111 1112 SVN_ERR(init_client_context(&(opt_baton->ctx), 1113 non_interactive, 1114 username, 1115 password, 1116 config_dir, 1117 opt_baton->url, 1118 no_auth_cache, 1119 trust_unknown_ca, 1120 trust_cn_mismatch, 1121 trust_expired, 1122 trust_not_yet_valid, 1123 trust_other_failure, 1124 config_options, 1125 pool)); 1126 1127 err = svn_client_open_ra_session2(&(opt_baton->session), 1128 opt_baton->url, NULL, 1129 opt_baton->ctx, pool, pool); 1130 1131 /* Have sane opt_baton->start_revision and end_revision defaults if 1132 unspecified. */ 1133 if (!err) 1134 err = svn_ra_get_latest_revnum(opt_baton->session, &latest_revision, pool); 1135 1136 /* Make sure any provided revisions make sense. */ 1137 if (!err) 1138 err = validate_and_resolve_revisions(opt_baton, latest_revision, pool); 1139 1140 /* Dispatch the subcommand */ 1141 if (!err) 1142 err = (*subcommand->cmd_func)(os, opt_baton, pool); 1143 1144 if (err && err->apr_err == SVN_ERR_AUTHN_FAILED && non_interactive) 1145 { 1146 return svn_error_quick_wrap(err, 1147 _("Authentication failed and interactive" 1148 " prompting is disabled; see the" 1149 " --force-interactive option")); 1150 } 1151 else if (err) 1152 return err; 1153 else 1154 return SVN_NO_ERROR; 1155} 1156 1157int 1158main(int argc, const char *argv[]) 1159{ 1160 apr_pool_t *pool; 1161 int exit_code = EXIT_SUCCESS; 1162 svn_error_t *err; 1163 1164 /* Initialize the app. */ 1165 if (svn_cmdline_init("svnrdump", stderr) != EXIT_SUCCESS) 1166 return EXIT_FAILURE; 1167 1168 /* Create our top-level pool. Use a separate mutexless allocator, 1169 * given this application is single threaded. 1170 */ 1171 pool = apr_allocator_owner_get(svn_pool_create_allocator(FALSE)); 1172 1173 err = sub_main(&exit_code, argc, argv, pool); 1174 1175 /* Flush stdout and report if it fails. It would be flushed on exit anyway 1176 but this makes sure that output is not silently lost if it fails. */ 1177 err = svn_error_compose_create(err, svn_cmdline_fflush(stdout)); 1178 1179 if (err) 1180 { 1181 exit_code = EXIT_FAILURE; 1182 svn_cmdline_handle_exit_error(err, NULL, "svnrdump: "); 1183 } 1184 1185 svn_pool_destroy(pool); 1186 1187 svn_cmdline__cancellation_exit(); 1188 1189 return exit_code; 1190} 1191