]> CyberLeo.Net >> Repos - FreeBSD/stable/10.git/blob - contrib/subversion/subversion/svnrdump/svnrdump.c
MFC r275385 (by bapt):
[FreeBSD/stable/10.git] / contrib / subversion / subversion / svnrdump / svnrdump.c
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 \f
48 /*** Cancellation ***/
49
50 /* A flag to see if we've been cancelled by the client or not. */
51 static volatile sig_atomic_t cancelled = FALSE;
52
53 /* A signal handler to support cancellation. */
54 static void
55 signal_handler(int signum)
56 {
57   apr_signal(signum, SIG_IGN);
58   cancelled = TRUE;
59 }
60
61 /* Our cancellation callback. */
62 static svn_error_t *
63 check_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 \f
73
74 static svn_opt_subcommand_t dump_cmd, load_cmd;
75
76 enum 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
102 static 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
121 static 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. */
181 struct 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 */
193 typedef 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  */
209 static svn_error_t *
210 replay_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. */
236 static svn_error_t *
237 replay_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  */
259 static svn_error_t *
260 replay_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. */
286 static svn_error_t *
287 replay_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  */
312 static svn_error_t *
313 init_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  */
404 static svn_error_t *
405 dump_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
420 static svn_error_t *
421 dump_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  */
479 static svn_error_t *
480 replay_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  */
559 static svn_error_t *
560 load_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  */
585 static const char *
586 ensure_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. */
596 static svn_error_t *
597 usage(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  */
608 static svn_error_t *
609 version(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'.  */
626 static svn_error_t *
627 dump_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'.  */
648 static svn_error_t *
649 load_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'.  */
663 static svn_error_t *
664 help_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  */
686 static svn_error_t *
687 validate_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  */
778 static svn_error_t *
779 sub_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
1136 int
1137 main(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 }