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