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