1251881Speter/*
2251881Speter *  svnrdump.c: Produce a dumpfile of a local or remote repository
3251881Speter *              without touching the filesystem, but for temporary files.
4251881Speter *
5251881Speter * ====================================================================
6251881Speter *    Licensed to the Apache Software Foundation (ASF) under one
7251881Speter *    or more contributor license agreements.  See the NOTICE file
8251881Speter *    distributed with this work for additional information
9251881Speter *    regarding copyright ownership.  The ASF licenses this file
10251881Speter *    to you under the Apache License, Version 2.0 (the
11251881Speter *    "License"); you may not use this file except in compliance
12251881Speter *    with the License.  You may obtain a copy of the License at
13251881Speter *
14251881Speter *      http://www.apache.org/licenses/LICENSE-2.0
15251881Speter *
16251881Speter *    Unless required by applicable law or agreed to in writing,
17251881Speter *    software distributed under the License is distributed on an
18251881Speter *    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
19251881Speter *    KIND, either express or implied.  See the License for the
20251881Speter *    specific language governing permissions and limitations
21251881Speter *    under the License.
22251881Speter * ====================================================================
23251881Speter */
24251881Speter
25251881Speter#include <apr_signal.h>
26251881Speter#include <apr_uri.h>
27251881Speter
28251881Speter#include "svn_pools.h"
29251881Speter#include "svn_cmdline.h"
30251881Speter#include "svn_client.h"
31251881Speter#include "svn_hash.h"
32251881Speter#include "svn_ra.h"
33251881Speter#include "svn_repos.h"
34251881Speter#include "svn_path.h"
35251881Speter#include "svn_utf.h"
36251881Speter#include "svn_private_config.h"
37251881Speter#include "svn_string.h"
38251881Speter#include "svn_props.h"
39251881Speter
40251881Speter#include "svnrdump.h"
41251881Speter
42299742Sdim#include "private/svn_repos_private.h"
43251881Speter#include "private/svn_cmdline_private.h"
44251881Speter#include "private/svn_ra_private.h"
45251881Speter
46251881Speter
47251881Speter
48251881Speter/*** Cancellation ***/
49251881Speter
50251881Speter/* A flag to see if we've been cancelled by the client or not. */
51251881Speterstatic volatile sig_atomic_t cancelled = FALSE;
52251881Speter
53251881Speter/* A signal handler to support cancellation. */
54251881Speterstatic void
55251881Spetersignal_handler(int signum)
56251881Speter{
57251881Speter  apr_signal(signum, SIG_IGN);
58251881Speter  cancelled = TRUE;
59251881Speter}
60251881Speter
61251881Speter/* Our cancellation callback. */
62251881Speterstatic svn_error_t *
63251881Spetercheck_cancel(void *baton)
64251881Speter{
65251881Speter  if (cancelled)
66251881Speter    return svn_error_create(SVN_ERR_CANCELLED, NULL, _("Caught signal"));
67251881Speter  else
68251881Speter    return SVN_NO_ERROR;
69251881Speter}
70251881Speter
71251881Speter
72251881Speter
73251881Speter
74251881Speterstatic svn_opt_subcommand_t dump_cmd, load_cmd;
75251881Speter
76251881Speterenum svn_svnrdump__longopt_t
77251881Speter  {
78251881Speter    opt_config_dir = SVN_OPT_FIRST_LONGOPT_ID,
79251881Speter    opt_config_option,
80251881Speter    opt_auth_username,
81251881Speter    opt_auth_password,
82251881Speter    opt_auth_nocache,
83251881Speter    opt_non_interactive,
84299742Sdim    opt_skip_revprop,
85251881Speter    opt_force_interactive,
86251881Speter    opt_incremental,
87251881Speter    opt_trust_server_cert,
88299742Sdim    opt_trust_server_cert_failures,
89251881Speter    opt_version
90251881Speter  };
91251881Speter
92251881Speter#define SVN_SVNRDUMP__BASE_OPTIONS opt_config_dir, \
93251881Speter                                   opt_config_option, \
94251881Speter                                   opt_auth_username, \
95251881Speter                                   opt_auth_password, \
96251881Speter                                   opt_auth_nocache, \
97251881Speter                                   opt_trust_server_cert, \
98299742Sdim                                   opt_trust_server_cert_failures, \
99251881Speter                                   opt_non_interactive, \
100251881Speter                                   opt_force_interactive
101251881Speter
102251881Speterstatic const svn_opt_subcommand_desc2_t svnrdump__cmd_table[] =
103251881Speter{
104251881Speter  { "dump", dump_cmd, { 0 },
105251881Speter    N_("usage: svnrdump dump URL [-r LOWER[:UPPER]]\n\n"
106251881Speter       "Dump revisions LOWER to UPPER of repository at remote URL to stdout\n"
107251881Speter       "in a 'dumpfile' portable format.  If only LOWER is given, dump that\n"
108251881Speter       "one revision.\n"),
109251881Speter    { 'r', 'q', opt_incremental, SVN_SVNRDUMP__BASE_OPTIONS } },
110251881Speter  { "load", load_cmd, { 0 },
111251881Speter    N_("usage: svnrdump load URL\n\n"
112251881Speter       "Load a 'dumpfile' given on stdin to a repository at remote URL.\n"),
113299742Sdim    { 'q', opt_skip_revprop, SVN_SVNRDUMP__BASE_OPTIONS } },
114251881Speter  { "help", 0, { "?", "h" },
115251881Speter    N_("usage: svnrdump help [SUBCOMMAND...]\n\n"
116251881Speter       "Describe the usage of this program or its subcommands.\n"),
117251881Speter    { 0 } },
118251881Speter  { NULL, NULL, { 0 }, NULL, { 0 } }
119251881Speter};
120251881Speter
121251881Speterstatic const apr_getopt_option_t svnrdump__options[] =
122251881Speter  {
123251881Speter    {"revision",     'r', 1,
124251881Speter                      N_("specify revision number ARG (or X:Y range)")},
125251881Speter    {"quiet",         'q', 0,
126251881Speter                      N_("no progress (only errors) to stderr")},
127251881Speter    {"incremental",   opt_incremental, 0,
128251881Speter                      N_("dump incrementally")},
129299742Sdim    {"skip-revprop",  opt_skip_revprop, 1,
130299742Sdim                      N_("skip revision property ARG (e.g., \"svn:author\")")},
131251881Speter    {"config-dir",    opt_config_dir, 1,
132251881Speter                      N_("read user configuration files from directory ARG")},
133251881Speter    {"username",      opt_auth_username, 1,
134251881Speter                      N_("specify a username ARG")},
135251881Speter    {"password",      opt_auth_password, 1,
136251881Speter                      N_("specify a password ARG")},
137251881Speter    {"non-interactive", opt_non_interactive, 0,
138251881Speter                      N_("do no interactive prompting (default is to prompt\n"
139251881Speter                         "                             "
140251881Speter                         "only if standard input is a terminal device)")},
141251881Speter    {"force-interactive", opt_force_interactive, 0,
142251881Speter                      N_("do interactive prompting even if standard input\n"
143251881Speter                         "                             "
144251881Speter                         "is not a terminal device")},
145251881Speter    {"no-auth-cache", opt_auth_nocache, 0,
146251881Speter                      N_("do not cache authentication tokens")},
147251881Speter    {"help",          'h', 0,
148251881Speter                      N_("display this help")},
149251881Speter    {"version",       opt_version, 0,
150251881Speter                      N_("show program version information")},
151251881Speter    {"config-option", opt_config_option, 1,
152251881Speter                      N_("set user configuration option in the format:\n"
153251881Speter                         "                             "
154251881Speter                         "    FILE:SECTION:OPTION=[VALUE]\n"
155251881Speter                         "                             "
156251881Speter                         "For example:\n"
157251881Speter                         "                             "
158251881Speter                         "    servers:global:http-library=serf")},
159299742Sdim  {"trust-server-cert", opt_trust_server_cert, 0,
160299742Sdim                    N_("deprecated; same as\n"
161299742Sdim                       "                             "
162299742Sdim                       "--trust-server-cert-failures=unknown-ca")},
163299742Sdim  {"trust-server-cert-failures", opt_trust_server_cert_failures, 1,
164299742Sdim                    N_("with --non-interactive, accept SSL server\n"
165299742Sdim                       "                             "
166299742Sdim                       "certificates with failures; ARG is comma-separated\n"
167299742Sdim                       "                             "
168299742Sdim                       "list of 'unknown-ca' (Unknown Authority),\n"
169299742Sdim                       "                             "
170299742Sdim                       "'cn-mismatch' (Hostname mismatch), 'expired'\n"
171299742Sdim                       "                             "
172299742Sdim                       "(Expired certificate), 'not-yet-valid' (Not yet\n"
173299742Sdim                       "                             "
174299742Sdim                       "valid certificate) and 'other' (all other not\n"
175299742Sdim                       "                             "
176299742Sdim                       "separately classified certificate errors).")},
177251881Speter    {0, 0, 0, 0}
178251881Speter  };
179251881Speter
180251881Speter/* Baton for the RA replay session. */
181251881Speterstruct replay_baton {
182251881Speter  /* A backdoor ra session for fetching information. */
183251881Speter  svn_ra_session_t *extra_ra_session;
184251881Speter
185251881Speter  /* The output stream */
186251881Speter  svn_stream_t *stdout_stream;
187251881Speter
188251881Speter  /* Whether to be quiet. */
189251881Speter  svn_boolean_t quiet;
190251881Speter};
191251881Speter
192251881Speter/* Option set */
193251881Spetertypedef struct opt_baton_t {
194251881Speter  svn_client_ctx_t *ctx;
195251881Speter  svn_ra_session_t *session;
196251881Speter  const char *url;
197251881Speter  svn_boolean_t help;
198251881Speter  svn_boolean_t version;
199251881Speter  svn_opt_revision_t start_revision;
200251881Speter  svn_opt_revision_t end_revision;
201251881Speter  svn_boolean_t quiet;
202251881Speter  svn_boolean_t incremental;
203299742Sdim  apr_hash_t *skip_revprops;
204251881Speter} opt_baton_t;
205251881Speter
206251881Speter/* Print dumpstream-formatted information about REVISION.
207251881Speter * Implements the `svn_ra_replay_revstart_callback_t' interface.
208251881Speter */
209251881Speterstatic svn_error_t *
210251881Speterreplay_revstart(svn_revnum_t revision,
211251881Speter                void *replay_baton,
212251881Speter                const svn_delta_editor_t **editor,
213251881Speter                void **edit_baton,
214251881Speter                apr_hash_t *rev_props,
215251881Speter                apr_pool_t *pool)
216251881Speter{
217251881Speter  struct replay_baton *rb = replay_baton;
218251881Speter  apr_hash_t *normal_props;
219251881Speter
220299742Sdim  /* Normalize and dump the revprops */
221251881Speter  SVN_ERR(svn_rdump__normalize_props(&normal_props, rev_props, pool));
222299742Sdim  SVN_ERR(svn_repos__dump_revision_record(rb->stdout_stream, revision, NULL,
223299742Sdim                                          normal_props,
224299742Sdim                                          TRUE /*props_section_always*/,
225299742Sdim                                          pool));
226251881Speter
227251881Speter  SVN_ERR(svn_rdump__get_dump_editor(editor, edit_baton, revision,
228251881Speter                                     rb->stdout_stream, rb->extra_ra_session,
229251881Speter                                     NULL, check_cancel, NULL, pool));
230251881Speter
231251881Speter  return SVN_NO_ERROR;
232251881Speter}
233251881Speter
234251881Speter/* Print progress information about the dump of REVISION.
235251881Speter   Implements the `svn_ra_replay_revfinish_callback_t' interface. */
236251881Speterstatic svn_error_t *
237251881Speterreplay_revend(svn_revnum_t revision,
238251881Speter              void *replay_baton,
239251881Speter              const svn_delta_editor_t *editor,
240251881Speter              void *edit_baton,
241251881Speter              apr_hash_t *rev_props,
242251881Speter              apr_pool_t *pool)
243251881Speter{
244251881Speter  /* No resources left to free. */
245251881Speter  struct replay_baton *rb = replay_baton;
246251881Speter
247251881Speter  SVN_ERR(editor->close_edit(edit_baton, pool));
248251881Speter
249251881Speter  if (! rb->quiet)
250251881Speter    SVN_ERR(svn_cmdline_fprintf(stderr, pool, "* Dumped revision %lu.\n",
251251881Speter                                revision));
252251881Speter  return SVN_NO_ERROR;
253251881Speter}
254251881Speter
255251881Speter#ifdef USE_EV2_IMPL
256251881Speter/* Print dumpstream-formatted information about REVISION.
257251881Speter * Implements the `svn_ra_replay_revstart_callback_t' interface.
258251881Speter */
259251881Speterstatic svn_error_t *
260251881Speterreplay_revstart_v2(svn_revnum_t revision,
261251881Speter                   void *replay_baton,
262251881Speter                   svn_editor_t **editor,
263251881Speter                   apr_hash_t *rev_props,
264251881Speter                   apr_pool_t *pool)
265251881Speter{
266251881Speter  struct replay_baton *rb = replay_baton;
267251881Speter  apr_hash_t *normal_props;
268251881Speter
269299742Sdim  /* Normalize and dump the revprops */
270251881Speter  SVN_ERR(svn_rdump__normalize_props(&normal_props, rev_props, pool));
271299742Sdim  SVN_ERR(svn_repos__dump_revision_record(rb->stdout_stream, revision,
272299742Sdim                                          normal_props,
273299742Sdim                                          TRUE /*props_section_always*/,
274299742Sdim                                          pool));
275251881Speter
276251881Speter  SVN_ERR(svn_rdump__get_dump_editor_v2(editor, revision,
277251881Speter                                        rb->stdout_stream,
278251881Speter                                        rb->extra_ra_session,
279251881Speter                                        NULL, check_cancel, NULL, pool, pool));
280251881Speter
281251881Speter  return SVN_NO_ERROR;
282251881Speter}
283251881Speter
284251881Speter/* Print progress information about the dump of REVISION.
285251881Speter   Implements the `svn_ra_replay_revfinish_callback_t' interface. */
286251881Speterstatic svn_error_t *
287251881Speterreplay_revend_v2(svn_revnum_t revision,
288251881Speter                 void *replay_baton,
289251881Speter                 svn_editor_t *editor,
290251881Speter                 apr_hash_t *rev_props,
291251881Speter                 apr_pool_t *pool)
292251881Speter{
293251881Speter  /* No resources left to free. */
294251881Speter  struct replay_baton *rb = replay_baton;
295251881Speter
296251881Speter  SVN_ERR(svn_editor_complete(editor));
297251881Speter
298251881Speter  if (! rb->quiet)
299251881Speter    SVN_ERR(svn_cmdline_fprintf(stderr, pool, "* Dumped revision %lu.\n",
300251881Speter                                revision));
301251881Speter  return SVN_NO_ERROR;
302251881Speter}
303251881Speter#endif
304251881Speter
305251881Speter/* Initialize the RA layer, and set *CTX to a new client context baton
306251881Speter * allocated from POOL.  Use CONFIG_DIR and pass USERNAME, PASSWORD,
307251881Speter * CONFIG_DIR and NO_AUTH_CACHE to initialize the authorization baton.
308251881Speter * CONFIG_OPTIONS (if not NULL) is a list of configuration overrides.
309251881Speter * REPOS_URL is used to fiddle with server-specific configuration
310251881Speter * options.
311251881Speter */
312251881Speterstatic svn_error_t *
313251881Speterinit_client_context(svn_client_ctx_t **ctx_p,
314251881Speter                    svn_boolean_t non_interactive,
315251881Speter                    const char *username,
316251881Speter                    const char *password,
317251881Speter                    const char *config_dir,
318251881Speter                    const char *repos_url,
319251881Speter                    svn_boolean_t no_auth_cache,
320299742Sdim                    svn_boolean_t trust_unknown_ca,
321299742Sdim                    svn_boolean_t trust_cn_mismatch,
322299742Sdim                    svn_boolean_t trust_expired,
323299742Sdim                    svn_boolean_t trust_not_yet_valid,
324299742Sdim                    svn_boolean_t trust_other_failure,
325251881Speter                    apr_array_header_t *config_options,
326251881Speter                    apr_pool_t *pool)
327251881Speter{
328251881Speter  svn_client_ctx_t *ctx = NULL;
329251881Speter  svn_config_t *cfg_config, *cfg_servers;
330251881Speter
331251881Speter  SVN_ERR(svn_ra_initialize(pool));
332251881Speter
333251881Speter  SVN_ERR(svn_config_ensure(config_dir, pool));
334251881Speter  SVN_ERR(svn_client_create_context2(&ctx, NULL, pool));
335251881Speter
336251881Speter  SVN_ERR(svn_config_get_config(&(ctx->config), config_dir, pool));
337251881Speter
338251881Speter  if (config_options)
339251881Speter    SVN_ERR(svn_cmdline__apply_config_options(ctx->config, config_options,
340251881Speter                                              "svnrdump: ", "--config-option"));
341251881Speter
342251881Speter  cfg_config = svn_hash_gets(ctx->config, SVN_CONFIG_CATEGORY_CONFIG);
343251881Speter
344251881Speter  /* ### FIXME: This is a hack to work around the fact that our dump
345251881Speter     ### editor simply can't handle the way ra_serf violates the
346251881Speter     ### editor v1 drive ordering requirements.
347251881Speter     ###
348251881Speter     ### We'll override both the global value and server-specific one
349251881Speter     ### for the 'http-bulk-updates' and 'http-max-connections'
350251881Speter     ### options in order to get ra_serf to try a bulk-update if the
351251881Speter     ### server will allow it, or at least try to limit all its
352251881Speter     ### auxiliary GETs/PROPFINDs to happening (well-ordered) on a
353251881Speter     ### single server connection.
354251881Speter     ###
355251881Speter     ### See http://subversion.tigris.org/issues/show_bug.cgi?id=4116.
356251881Speter  */
357251881Speter  cfg_servers = svn_hash_gets(ctx->config, SVN_CONFIG_CATEGORY_SERVERS);
358251881Speter  svn_config_set_bool(cfg_servers, SVN_CONFIG_SECTION_GLOBAL,
359251881Speter                      SVN_CONFIG_OPTION_HTTP_BULK_UPDATES, TRUE);
360251881Speter  svn_config_set_int64(cfg_servers, SVN_CONFIG_SECTION_GLOBAL,
361251881Speter                       SVN_CONFIG_OPTION_HTTP_MAX_CONNECTIONS, 2);
362251881Speter  if (cfg_servers)
363251881Speter    {
364251881Speter      apr_status_t status;
365251881Speter      apr_uri_t parsed_url;
366251881Speter
367251881Speter      status = apr_uri_parse(pool, repos_url, &parsed_url);
368251881Speter      if (! status)
369251881Speter        {
370251881Speter          const char *server_group;
371251881Speter
372251881Speter          server_group = svn_config_find_group(cfg_servers, parsed_url.hostname,
373251881Speter                                               SVN_CONFIG_SECTION_GROUPS, pool);
374251881Speter          if (server_group)
375251881Speter            {
376251881Speter              svn_config_set_bool(cfg_servers, server_group,
377251881Speter                                  SVN_CONFIG_OPTION_HTTP_BULK_UPDATES, TRUE);
378251881Speter              svn_config_set_int64(cfg_servers, server_group,
379251881Speter                                   SVN_CONFIG_OPTION_HTTP_MAX_CONNECTIONS, 2);
380251881Speter            }
381251881Speter        }
382251881Speter    }
383251881Speter
384251881Speter  /* Set up our cancellation support. */
385251881Speter  ctx->cancel_func = check_cancel;
386251881Speter
387251881Speter  /* Default authentication providers for non-interactive use */
388299742Sdim  SVN_ERR(svn_cmdline_create_auth_baton2(&(ctx->auth_baton), non_interactive,
389299742Sdim                                         username, password, config_dir,
390299742Sdim                                         no_auth_cache, trust_unknown_ca,
391299742Sdim                                         trust_cn_mismatch, trust_expired,
392299742Sdim                                         trust_not_yet_valid,
393299742Sdim                                         trust_other_failure,
394299742Sdim                                         cfg_config, ctx->cancel_func,
395299742Sdim                                         ctx->cancel_baton, pool));
396251881Speter  *ctx_p = ctx;
397251881Speter  return SVN_NO_ERROR;
398251881Speter}
399251881Speter
400251881Speter/* Print a revision record header for REVISION to STDOUT_STREAM.  Use
401251881Speter * SESSION to contact the repository for revision properties and
402251881Speter * such.
403251881Speter */
404251881Speterstatic svn_error_t *
405251881Speterdump_revision_header(svn_ra_session_t *session,
406251881Speter                     svn_stream_t *stdout_stream,
407251881Speter                     svn_revnum_t revision,
408251881Speter                     apr_pool_t *pool)
409251881Speter{
410251881Speter  apr_hash_t *prophash;
411251881Speter
412251881Speter  SVN_ERR(svn_ra_rev_proplist(session, revision, &prophash, pool));
413299742Sdim  SVN_ERR(svn_repos__dump_revision_record(stdout_stream, revision, NULL,
414299742Sdim                                          prophash,
415299742Sdim                                          TRUE /*props_section_always*/,
416299742Sdim                                          pool));
417251881Speter  return SVN_NO_ERROR;
418251881Speter}
419251881Speter
420251881Speterstatic svn_error_t *
421251881Speterdump_initial_full_revision(svn_ra_session_t *session,
422251881Speter                           svn_ra_session_t *extra_ra_session,
423251881Speter                           svn_stream_t *stdout_stream,
424251881Speter                           svn_revnum_t revision,
425251881Speter                           svn_boolean_t quiet,
426251881Speter                           apr_pool_t *pool)
427251881Speter{
428251881Speter  const svn_ra_reporter3_t *reporter;
429251881Speter  void *report_baton;
430251881Speter  const svn_delta_editor_t *dump_editor;
431251881Speter  void *dump_baton;
432251881Speter  const char *session_url, *source_relpath;
433251881Speter
434251881Speter  /* Determine whether we're dumping the repository root URL or some
435251881Speter     child thereof.  If we're dumping a subtree of the repository
436251881Speter     rather than the root, we have to jump through some hoops to make
437251881Speter     our update-driven dump generation work the way a replay-driven
438251881Speter     one would.
439251881Speter
440251881Speter     See http://subversion.tigris.org/issues/show_bug.cgi?id=4101
441251881Speter  */
442251881Speter  SVN_ERR(svn_ra_get_session_url(session, &session_url, pool));
443251881Speter  SVN_ERR(svn_ra_get_path_relative_to_root(session, &source_relpath,
444251881Speter                                           session_url, pool));
445251881Speter
446251881Speter  /* Start with a revision record header. */
447251881Speter  SVN_ERR(dump_revision_header(session, stdout_stream, revision, pool));
448251881Speter
449251881Speter  /* Then, we'll drive the dump editor with what would look like a
450251881Speter     full checkout of the repository as it looked in START_REVISION.
451251881Speter     We do this by manufacturing a basic 'report' to the update
452251881Speter     reporter, telling it that we have nothing to start with.  The
453251881Speter     delta between nothing and everything-at-REV is, effectively, a
454251881Speter     full dump of REV. */
455251881Speter  SVN_ERR(svn_rdump__get_dump_editor(&dump_editor, &dump_baton, revision,
456251881Speter                                     stdout_stream, extra_ra_session,
457251881Speter                                     source_relpath, check_cancel, NULL, pool));
458251881Speter  SVN_ERR(svn_ra_do_update3(session, &reporter, &report_baton, revision,
459251881Speter                            "", svn_depth_infinity, FALSE, FALSE,
460251881Speter                            dump_editor, dump_baton, pool, pool));
461251881Speter  SVN_ERR(reporter->set_path(report_baton, "", revision,
462251881Speter                             svn_depth_infinity, TRUE, NULL, pool));
463251881Speter  SVN_ERR(reporter->finish_report(report_baton, pool));
464251881Speter
465251881Speter  /* All finished with START_REVISION! */
466251881Speter  if (! quiet)
467251881Speter    SVN_ERR(svn_cmdline_fprintf(stderr, pool, "* Dumped revision %lu.\n",
468251881Speter                                revision));
469251881Speter
470251881Speter  return SVN_NO_ERROR;
471251881Speter}
472251881Speter
473251881Speter/* Replay revisions START_REVISION thru END_REVISION (inclusive) of
474251881Speter * the repository URL at which SESSION is rooted, using callbacks
475251881Speter * which generate Subversion repository dumpstreams describing the
476251881Speter * changes made in those revisions.  If QUIET is set, don't generate
477251881Speter * progress messages.
478251881Speter */
479251881Speterstatic svn_error_t *
480251881Speterreplay_revisions(svn_ra_session_t *session,
481251881Speter                 svn_ra_session_t *extra_ra_session,
482251881Speter                 svn_revnum_t start_revision,
483251881Speter                 svn_revnum_t end_revision,
484251881Speter                 svn_boolean_t quiet,
485251881Speter                 svn_boolean_t incremental,
486251881Speter                 apr_pool_t *pool)
487251881Speter{
488251881Speter  struct replay_baton *replay_baton;
489251881Speter  const char *uuid;
490251881Speter  svn_stream_t *stdout_stream;
491251881Speter
492251881Speter  SVN_ERR(svn_stream_for_stdout(&stdout_stream, pool));
493251881Speter
494251881Speter  replay_baton = apr_pcalloc(pool, sizeof(*replay_baton));
495251881Speter  replay_baton->stdout_stream = stdout_stream;
496251881Speter  replay_baton->extra_ra_session = extra_ra_session;
497251881Speter  replay_baton->quiet = quiet;
498251881Speter
499251881Speter  /* Write the magic header and UUID */
500251881Speter  SVN_ERR(svn_stream_printf(stdout_stream, pool,
501251881Speter                            SVN_REPOS_DUMPFILE_MAGIC_HEADER ": %d\n\n",
502251881Speter                            SVN_REPOS_DUMPFILE_FORMAT_VERSION));
503251881Speter  SVN_ERR(svn_ra_get_uuid2(session, &uuid, pool));
504251881Speter  SVN_ERR(svn_stream_printf(stdout_stream, pool,
505251881Speter                            SVN_REPOS_DUMPFILE_UUID ": %s\n\n", uuid));
506251881Speter
507251881Speter  /* Fake revision 0 if necessary */
508251881Speter  if (start_revision == 0)
509251881Speter    {
510251881Speter      SVN_ERR(dump_revision_header(session, stdout_stream,
511251881Speter                                   start_revision, pool));
512251881Speter
513251881Speter      /* Revision 0 has no tree changes, so we're done. */
514251881Speter      if (! quiet)
515251881Speter        SVN_ERR(svn_cmdline_fprintf(stderr, pool, "* Dumped revision %lu.\n",
516251881Speter                                    start_revision));
517251881Speter      start_revision++;
518251881Speter
519251881Speter      /* If our first revision is 0, we can treat this as an
520251881Speter         incremental dump. */
521251881Speter      incremental = TRUE;
522251881Speter    }
523251881Speter
524251881Speter  /* If what remains to be dumped is not going to be dumped
525251881Speter     incrementally, then dump the first revision in full. */
526251881Speter  if (!incremental)
527251881Speter    {
528251881Speter      SVN_ERR(dump_initial_full_revision(session, extra_ra_session,
529251881Speter                                         stdout_stream, start_revision,
530251881Speter                                         quiet, pool));
531251881Speter      start_revision++;
532251881Speter    }
533251881Speter
534251881Speter  /* If there are still revisions left to be dumped, do so. */
535251881Speter  if (start_revision <= end_revision)
536251881Speter    {
537251881Speter#ifndef USE_EV2_IMPL
538251881Speter      SVN_ERR(svn_ra_replay_range(session, start_revision, end_revision,
539251881Speter                                  0, TRUE, replay_revstart, replay_revend,
540251881Speter                                  replay_baton, pool));
541251881Speter#else
542251881Speter      SVN_ERR(svn_ra__replay_range_ev2(session, start_revision, end_revision,
543251881Speter                                       0, TRUE, replay_revstart_v2,
544251881Speter                                       replay_revend_v2, replay_baton,
545251881Speter                                       NULL, NULL, NULL, NULL, pool));
546251881Speter#endif
547251881Speter    }
548251881Speter
549251881Speter  SVN_ERR(svn_stream_close(stdout_stream));
550251881Speter  return SVN_NO_ERROR;
551251881Speter}
552251881Speter
553251881Speter/* Read a dumpstream from stdin, and use it to feed a loader capable
554251881Speter * of transmitting that information to the repository located at URL
555251881Speter * (to which SESSION has been opened).  AUX_SESSION is a second RA
556251881Speter * session opened to the same URL for performing auxiliary out-of-band
557251881Speter * operations.
558251881Speter */
559251881Speterstatic svn_error_t *
560251881Speterload_revisions(svn_ra_session_t *session,
561251881Speter               svn_ra_session_t *aux_session,
562251881Speter               const char *url,
563251881Speter               svn_boolean_t quiet,
564299742Sdim               apr_hash_t *skip_revprops,
565251881Speter               apr_pool_t *pool)
566251881Speter{
567251881Speter  apr_file_t *stdin_file;
568251881Speter  svn_stream_t *stdin_stream;
569251881Speter
570251881Speter  apr_file_open_stdin(&stdin_file, pool);
571251881Speter  stdin_stream = svn_stream_from_aprfile2(stdin_file, FALSE, pool);
572251881Speter
573251881Speter  SVN_ERR(svn_rdump__load_dumpstream(stdin_stream, session, aux_session,
574299742Sdim                                     quiet, skip_revprops,
575299742Sdim                                     check_cancel, NULL, pool));
576251881Speter
577251881Speter  SVN_ERR(svn_stream_close(stdin_stream));
578251881Speter
579251881Speter  return SVN_NO_ERROR;
580251881Speter}
581251881Speter
582251881Speter/* Return a program name for this program, the basename of the path
583251881Speter * represented by PROGNAME if not NULL; use "svnrdump" otherwise.
584251881Speter */
585251881Speterstatic const char *
586251881Speterensure_appname(const char *progname,
587251881Speter               apr_pool_t *pool)
588251881Speter{
589251881Speter  if (!progname)
590251881Speter    return "svnrdump";
591251881Speter
592251881Speter  return svn_dirent_basename(svn_dirent_internal_style(progname, pool), NULL);
593251881Speter}
594251881Speter
595251881Speter/* Print a simple usage string. */
596251881Speterstatic svn_error_t *
597251881Speterusage(const char *progname,
598251881Speter      apr_pool_t *pool)
599251881Speter{
600251881Speter  return svn_cmdline_fprintf(stderr, pool,
601251881Speter                             _("Type '%s help' for usage.\n"),
602251881Speter                             ensure_appname(progname, pool));
603251881Speter}
604251881Speter
605251881Speter/* Print information about the version of this program and dependent
606251881Speter * modules.
607251881Speter */
608251881Speterstatic svn_error_t *
609251881Speterversion(const char *progname,
610251881Speter        svn_boolean_t quiet,
611251881Speter        apr_pool_t *pool)
612251881Speter{
613251881Speter  svn_stringbuf_t *version_footer =
614251881Speter    svn_stringbuf_create(_("The following repository access (RA) modules "
615251881Speter                           "are available:\n\n"),
616251881Speter                         pool);
617251881Speter
618251881Speter  SVN_ERR(svn_ra_print_modules(version_footer, pool));
619251881Speter  return svn_opt_print_help4(NULL, ensure_appname(progname, pool),
620251881Speter                             TRUE, quiet, FALSE, version_footer->data,
621251881Speter                             NULL, NULL, NULL, NULL, NULL, pool);
622251881Speter}
623251881Speter
624251881Speter
625251881Speter/* Handle the "dump" subcommand.  Implements `svn_opt_subcommand_t'.  */
626251881Speterstatic svn_error_t *
627251881Speterdump_cmd(apr_getopt_t *os,
628251881Speter         void *baton,
629251881Speter         apr_pool_t *pool)
630251881Speter{
631251881Speter  opt_baton_t *opt_baton = baton;
632251881Speter  svn_ra_session_t *extra_ra_session;
633251881Speter  const char *repos_root;
634251881Speter
635251881Speter  SVN_ERR(svn_client_open_ra_session2(&extra_ra_session,
636251881Speter                                      opt_baton->url, NULL,
637251881Speter                                      opt_baton->ctx, pool, pool));
638251881Speter  SVN_ERR(svn_ra_get_repos_root2(extra_ra_session, &repos_root, pool));
639251881Speter  SVN_ERR(svn_ra_reparent(extra_ra_session, repos_root, pool));
640251881Speter
641251881Speter  return replay_revisions(opt_baton->session, extra_ra_session,
642251881Speter                          opt_baton->start_revision.value.number,
643251881Speter                          opt_baton->end_revision.value.number,
644251881Speter                          opt_baton->quiet, opt_baton->incremental, pool);
645251881Speter}
646251881Speter
647251881Speter/* Handle the "load" subcommand.  Implements `svn_opt_subcommand_t'.  */
648251881Speterstatic svn_error_t *
649251881Speterload_cmd(apr_getopt_t *os,
650251881Speter         void *baton,
651251881Speter         apr_pool_t *pool)
652251881Speter{
653251881Speter  opt_baton_t *opt_baton = baton;
654251881Speter  svn_ra_session_t *aux_session;
655251881Speter
656251881Speter  SVN_ERR(svn_client_open_ra_session2(&aux_session, opt_baton->url, NULL,
657251881Speter                                      opt_baton->ctx, pool, pool));
658251881Speter  return load_revisions(opt_baton->session, aux_session, opt_baton->url,
659299742Sdim                        opt_baton->quiet, opt_baton->skip_revprops, pool);
660251881Speter}
661251881Speter
662251881Speter/* Handle the "help" subcommand.  Implements `svn_opt_subcommand_t'.  */
663251881Speterstatic svn_error_t *
664251881Speterhelp_cmd(apr_getopt_t *os,
665251881Speter         void *baton,
666251881Speter         apr_pool_t *pool)
667251881Speter{
668251881Speter  const char *header =
669251881Speter    _("general usage: svnrdump SUBCOMMAND URL [-r LOWER[:UPPER]]\n"
670299742Sdim      "Subversion remote repository dump and load tool.\n"
671251881Speter      "Type 'svnrdump help <subcommand>' for help on a specific subcommand.\n"
672251881Speter      "Type 'svnrdump --version' to see the program version and RA modules.\n"
673251881Speter      "\n"
674251881Speter      "Available subcommands:\n");
675251881Speter
676251881Speter  return svn_opt_print_help4(os, "svnrdump", FALSE, FALSE, FALSE, NULL,
677251881Speter                             header, svnrdump__cmd_table, svnrdump__options,
678251881Speter                             NULL, NULL, pool);
679251881Speter}
680251881Speter
681251881Speter/* Examine the OPT_BATON's 'start_revision' and 'end_revision'
682251881Speter * members, making sure that they make sense (in general, and as
683251881Speter * applied to a repository whose current youngest revision is
684251881Speter * LATEST_REVISION).
685251881Speter */
686251881Speterstatic svn_error_t *
687251881Spetervalidate_and_resolve_revisions(opt_baton_t *opt_baton,
688251881Speter                               svn_revnum_t latest_revision,
689251881Speter                               apr_pool_t *pool)
690251881Speter{
691251881Speter  svn_revnum_t provided_start_rev = SVN_INVALID_REVNUM;
692251881Speter
693251881Speter  /* Ensure that the start revision is something we can handle.  We
694251881Speter     want a number >= 0.  If unspecified, make it a number (r0) --
695251881Speter     anything else is bogus.  */
696251881Speter  if (opt_baton->start_revision.kind == svn_opt_revision_number)
697251881Speter    {
698251881Speter      provided_start_rev = opt_baton->start_revision.value.number;
699251881Speter    }
700251881Speter  else if (opt_baton->start_revision.kind == svn_opt_revision_head)
701251881Speter    {
702251881Speter      opt_baton->start_revision.kind = svn_opt_revision_number;
703251881Speter      opt_baton->start_revision.value.number = latest_revision;
704251881Speter    }
705251881Speter  else if (opt_baton->start_revision.kind == svn_opt_revision_unspecified)
706251881Speter    {
707251881Speter      opt_baton->start_revision.kind = svn_opt_revision_number;
708251881Speter      opt_baton->start_revision.value.number = 0;
709251881Speter    }
710251881Speter
711251881Speter  if (opt_baton->start_revision.kind != svn_opt_revision_number)
712251881Speter    {
713251881Speter      return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
714251881Speter                              _("Unsupported revision specifier used; use "
715251881Speter                                "only integer values or 'HEAD'"));
716251881Speter    }
717251881Speter
718251881Speter  if ((opt_baton->start_revision.value.number < 0) ||
719251881Speter      (opt_baton->start_revision.value.number > latest_revision))
720251881Speter    {
721251881Speter      return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
722251881Speter                               _("Revision '%ld' does not exist"),
723251881Speter                               opt_baton->start_revision.value.number);
724251881Speter    }
725251881Speter
726251881Speter  /* Ensure that the end revision is something we can handle.  We want
727251881Speter     a number <= the youngest, and > the start revision.  If
728251881Speter     unspecified, make it a number (start_revision + 1 if that was
729251881Speter     specified, the youngest revision in the repository otherwise) --
730251881Speter     anything else is bogus.  */
731251881Speter  if (opt_baton->end_revision.kind == svn_opt_revision_unspecified)
732251881Speter    {
733251881Speter      opt_baton->end_revision.kind = svn_opt_revision_number;
734251881Speter      if (SVN_IS_VALID_REVNUM(provided_start_rev))
735251881Speter        opt_baton->end_revision.value.number = provided_start_rev;
736251881Speter      else
737251881Speter        opt_baton->end_revision.value.number = latest_revision;
738251881Speter    }
739251881Speter  else if (opt_baton->end_revision.kind == svn_opt_revision_head)
740251881Speter    {
741251881Speter      opt_baton->end_revision.kind = svn_opt_revision_number;
742251881Speter      opt_baton->end_revision.value.number = latest_revision;
743251881Speter    }
744251881Speter
745251881Speter  if (opt_baton->end_revision.kind != svn_opt_revision_number)
746251881Speter    {
747251881Speter      return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
748251881Speter                              _("Unsupported revision specifier used; use "
749251881Speter                                "only integer values or 'HEAD'"));
750251881Speter    }
751251881Speter
752251881Speter  if ((opt_baton->end_revision.value.number < 0) ||
753251881Speter      (opt_baton->end_revision.value.number > latest_revision))
754251881Speter    {
755251881Speter      return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
756251881Speter                               _("Revision '%ld' does not exist"),
757251881Speter                               opt_baton->end_revision.value.number);
758251881Speter    }
759251881Speter
760251881Speter  /* Finally, make sure that the end revision is younger than the
761251881Speter     start revision.  We don't do "backwards" 'round here.  */
762251881Speter  if (opt_baton->end_revision.value.number <
763251881Speter      opt_baton->start_revision.value.number)
764251881Speter    {
765251881Speter      return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
766251881Speter                              _("LOWER revision cannot be greater than "
767251881Speter                                "UPPER revision; consider reversing your "
768251881Speter                                "revision range"));
769251881Speter    }
770251881Speter  return SVN_NO_ERROR;
771251881Speter}
772251881Speter
773299742Sdim/*
774299742Sdim * On success, leave *EXIT_CODE untouched and return SVN_NO_ERROR. On error,
775299742Sdim * either return an error to be displayed, or set *EXIT_CODE to non-zero and
776299742Sdim * return SVN_NO_ERROR.
777299742Sdim */
778299742Sdimstatic svn_error_t *
779299742Sdimsub_main(int *exit_code, int argc, const char *argv[], apr_pool_t *pool)
780251881Speter{
781251881Speter  svn_error_t *err = SVN_NO_ERROR;
782251881Speter  const svn_opt_subcommand_desc2_t *subcommand = NULL;
783251881Speter  opt_baton_t *opt_baton;
784251881Speter  svn_revnum_t latest_revision = SVN_INVALID_REVNUM;
785251881Speter  const char *config_dir = NULL;
786251881Speter  const char *username = NULL;
787251881Speter  const char *password = NULL;
788251881Speter  svn_boolean_t no_auth_cache = FALSE;
789299742Sdim  svn_boolean_t trust_unknown_ca = FALSE;
790299742Sdim  svn_boolean_t trust_cn_mismatch = FALSE;
791299742Sdim  svn_boolean_t trust_expired = FALSE;
792299742Sdim  svn_boolean_t trust_not_yet_valid = FALSE;
793299742Sdim  svn_boolean_t trust_other_failure = FALSE;
794251881Speter  svn_boolean_t non_interactive = FALSE;
795251881Speter  svn_boolean_t force_interactive = FALSE;
796251881Speter  apr_array_header_t *config_options = NULL;
797251881Speter  apr_getopt_t *os;
798251881Speter  const char *first_arg;
799251881Speter  apr_array_header_t *received_opts;
800251881Speter  int i;
801251881Speter
802251881Speter  opt_baton = apr_pcalloc(pool, sizeof(*opt_baton));
803251881Speter  opt_baton->start_revision.kind = svn_opt_revision_unspecified;
804251881Speter  opt_baton->end_revision.kind = svn_opt_revision_unspecified;
805251881Speter  opt_baton->url = NULL;
806299742Sdim  opt_baton->skip_revprops = apr_hash_make(pool);
807251881Speter
808299742Sdim  SVN_ERR(svn_cmdline__getopt_init(&os, argc, argv, pool));
809251881Speter
810251881Speter  os->interleave = TRUE; /* Options and arguments can be interleaved */
811251881Speter
812251881Speter  /* Set up our cancellation support. */
813251881Speter  apr_signal(SIGINT, signal_handler);
814251881Speter#ifdef SIGBREAK
815251881Speter  /* SIGBREAK is a Win32 specific signal generated by ctrl-break. */
816251881Speter  apr_signal(SIGBREAK, signal_handler);
817251881Speter#endif
818251881Speter#ifdef SIGHUP
819251881Speter  apr_signal(SIGHUP, signal_handler);
820251881Speter#endif
821251881Speter#ifdef SIGTERM
822251881Speter  apr_signal(SIGTERM, signal_handler);
823251881Speter#endif
824251881Speter#ifdef SIGPIPE
825251881Speter  /* Disable SIGPIPE generation for the platforms that have it. */
826251881Speter  apr_signal(SIGPIPE, SIG_IGN);
827251881Speter#endif
828251881Speter#ifdef SIGXFSZ
829251881Speter  /* Disable SIGXFSZ generation for the platforms that have it, otherwise
830251881Speter   * working with large files when compiled against an APR that doesn't have
831251881Speter   * large file support will crash the program, which is uncool. */
832251881Speter  apr_signal(SIGXFSZ, SIG_IGN);
833251881Speter#endif
834251881Speter
835251881Speter  received_opts = apr_array_make(pool, SVN_OPT_MAX_OPTIONS, sizeof(int));
836251881Speter
837251881Speter  while (1)
838251881Speter    {
839251881Speter      int opt;
840251881Speter      const char *opt_arg;
841251881Speter      apr_status_t status = apr_getopt_long(os, svnrdump__options, &opt,
842251881Speter                                            &opt_arg);
843251881Speter
844251881Speter      if (APR_STATUS_IS_EOF(status))
845251881Speter        break;
846251881Speter      if (status != APR_SUCCESS)
847251881Speter        {
848299742Sdim          SVN_ERR(usage(argv[0], pool));
849299742Sdim          *exit_code = EXIT_FAILURE;
850299742Sdim          return SVN_NO_ERROR;
851251881Speter        }
852251881Speter
853251881Speter      /* Stash the option code in an array before parsing it. */
854251881Speter      APR_ARRAY_PUSH(received_opts, int) = opt;
855251881Speter
856251881Speter      switch(opt)
857251881Speter        {
858251881Speter        case 'r':
859251881Speter          {
860251881Speter            /* Make sure we've not seen -r already. */
861251881Speter            if (opt_baton->start_revision.kind != svn_opt_revision_unspecified)
862251881Speter              {
863299742Sdim                return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
864299742Sdim                                        _("Multiple revision arguments "
865299742Sdim                                          "encountered; try '-r N:M' instead "
866299742Sdim                                          "of '-r N -r M'"));
867251881Speter              }
868251881Speter            /* Parse the -r argument. */
869251881Speter            if (svn_opt_parse_revision(&(opt_baton->start_revision),
870251881Speter                                       &(opt_baton->end_revision),
871251881Speter                                       opt_arg, pool) != 0)
872251881Speter              {
873251881Speter                const char *utf8_opt_arg;
874299742Sdim                SVN_ERR(svn_utf_cstring_to_utf8(&utf8_opt_arg, opt_arg, pool));
875299742Sdim                return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
876299742Sdim                                         _("Syntax error in revision "
877299742Sdim                                           "argument '%s'"), utf8_opt_arg);
878251881Speter              }
879251881Speter          }
880251881Speter          break;
881251881Speter        case 'q':
882251881Speter          opt_baton->quiet = TRUE;
883251881Speter          break;
884251881Speter        case opt_config_dir:
885251881Speter          config_dir = opt_arg;
886251881Speter          break;
887251881Speter        case opt_version:
888251881Speter          opt_baton->version = TRUE;
889251881Speter          break;
890251881Speter        case 'h':
891251881Speter          opt_baton->help = TRUE;
892251881Speter          break;
893251881Speter        case opt_auth_username:
894299742Sdim          SVN_ERR(svn_utf_cstring_to_utf8(&username, opt_arg, pool));
895251881Speter          break;
896251881Speter        case opt_auth_password:
897299742Sdim          SVN_ERR(svn_utf_cstring_to_utf8(&password, opt_arg, pool));
898251881Speter          break;
899251881Speter        case opt_auth_nocache:
900251881Speter          no_auth_cache = TRUE;
901251881Speter          break;
902251881Speter        case opt_non_interactive:
903251881Speter          non_interactive = TRUE;
904251881Speter          break;
905251881Speter        case opt_force_interactive:
906251881Speter          force_interactive = TRUE;
907251881Speter          break;
908251881Speter        case opt_incremental:
909251881Speter          opt_baton->incremental = TRUE;
910251881Speter          break;
911299742Sdim        case opt_skip_revprop:
912299742Sdim          SVN_ERR(svn_utf_cstring_to_utf8(&opt_arg, opt_arg, pool));
913299742Sdim          svn_hash_sets(opt_baton->skip_revprops, opt_arg, opt_arg);
914251881Speter          break;
915299742Sdim        case opt_trust_server_cert: /* backward compat */
916299742Sdim          trust_unknown_ca = TRUE;
917299742Sdim          break;
918299742Sdim        case opt_trust_server_cert_failures:
919299742Sdim          SVN_ERR(svn_utf_cstring_to_utf8(&opt_arg, opt_arg, pool));
920299742Sdim          SVN_ERR(svn_cmdline__parse_trust_options(
921299742Sdim                      &trust_unknown_ca,
922299742Sdim                      &trust_cn_mismatch,
923299742Sdim                      &trust_expired,
924299742Sdim                      &trust_not_yet_valid,
925299742Sdim                      &trust_other_failure,
926299742Sdim                      opt_arg, pool));
927299742Sdim          break;
928251881Speter        case opt_config_option:
929251881Speter          if (!config_options)
930251881Speter              config_options =
931251881Speter                    apr_array_make(pool, 1,
932251881Speter                                   sizeof(svn_cmdline__config_argument_t*));
933251881Speter
934299742Sdim            SVN_ERR(svn_utf_cstring_to_utf8(&opt_arg, opt_arg, pool));
935299742Sdim            SVN_ERR(svn_cmdline__parse_config_option(config_options,
936299742Sdim                                                     opt_arg,
937299742Sdim                                                     "svnrdump: ",
938299742Sdim                                                     pool));
939251881Speter        }
940251881Speter    }
941251881Speter
942251881Speter  /* The --non-interactive and --force-interactive options are mutually
943251881Speter   * exclusive. */
944251881Speter  if (non_interactive && force_interactive)
945251881Speter    {
946299742Sdim      return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
947299742Sdim                              _("--non-interactive and --force-interactive "
948299742Sdim                                "are mutually exclusive"));
949251881Speter    }
950251881Speter
951251881Speter  if (opt_baton->help)
952251881Speter    {
953251881Speter      subcommand = svn_opt_get_canonical_subcommand2(svnrdump__cmd_table,
954251881Speter                                                     "help");
955251881Speter    }
956251881Speter  if (subcommand == NULL)
957251881Speter    {
958251881Speter      if (os->ind >= os->argc)
959251881Speter        {
960251881Speter          if (opt_baton->version)
961251881Speter            {
962251881Speter              /* Use the "help" subcommand to handle the "--version" option. */
963251881Speter              static const svn_opt_subcommand_desc2_t pseudo_cmd =
964251881Speter                { "--version", help_cmd, {0}, "",
965251881Speter                  {opt_version,  /* must accept its own option */
966251881Speter                   'q',  /* --quiet */
967251881Speter                  } };
968251881Speter              subcommand = &pseudo_cmd;
969251881Speter            }
970251881Speter
971251881Speter          else
972251881Speter            {
973299742Sdim              SVN_ERR(help_cmd(NULL, NULL, pool));
974299742Sdim              *exit_code = EXIT_FAILURE;
975299742Sdim              return SVN_NO_ERROR;
976251881Speter            }
977251881Speter        }
978251881Speter      else
979251881Speter        {
980251881Speter          first_arg = os->argv[os->ind++];
981251881Speter          subcommand = svn_opt_get_canonical_subcommand2(svnrdump__cmd_table,
982251881Speter                                                         first_arg);
983251881Speter
984251881Speter          if (subcommand == NULL)
985251881Speter            {
986251881Speter              const char *first_arg_utf8;
987299742Sdim              SVN_ERR(svn_utf_cstring_to_utf8(&first_arg_utf8, first_arg,
988299742Sdim                                              pool));
989251881Speter              svn_error_clear(
990251881Speter                svn_cmdline_fprintf(stderr, pool,
991251881Speter                                    _("Unknown subcommand: '%s'\n"),
992251881Speter                                    first_arg_utf8));
993299742Sdim              SVN_ERR(help_cmd(NULL, NULL, pool));
994299742Sdim              *exit_code = EXIT_FAILURE;
995299742Sdim              return SVN_NO_ERROR;
996251881Speter            }
997251881Speter        }
998251881Speter    }
999251881Speter
1000251881Speter  /* Check that the subcommand wasn't passed any inappropriate options. */
1001251881Speter  for (i = 0; i < received_opts->nelts; i++)
1002251881Speter    {
1003251881Speter      int opt_id = APR_ARRAY_IDX(received_opts, i, int);
1004251881Speter
1005251881Speter      /* All commands implicitly accept --help, so just skip over this
1006251881Speter         when we see it. Note that we don't want to include this option
1007251881Speter         in their "accepted options" list because it would be awfully
1008251881Speter         redundant to display it in every commands' help text. */
1009251881Speter      if (opt_id == 'h' || opt_id == '?')
1010251881Speter        continue;
1011251881Speter
1012251881Speter      if (! svn_opt_subcommand_takes_option3(subcommand, opt_id, NULL))
1013251881Speter        {
1014251881Speter          const char *optstr;
1015251881Speter          const apr_getopt_option_t *badopt =
1016251881Speter            svn_opt_get_option_from_code2(opt_id, svnrdump__options,
1017251881Speter                                          subcommand, pool);
1018251881Speter          svn_opt_format_option(&optstr, badopt, FALSE, pool);
1019251881Speter          if (subcommand->name[0] == '-')
1020299742Sdim            SVN_ERR(help_cmd(NULL, NULL, pool));
1021251881Speter          else
1022251881Speter            svn_error_clear(svn_cmdline_fprintf(
1023251881Speter                                stderr, pool,
1024251881Speter                                _("Subcommand '%s' doesn't accept option '%s'\n"
1025251881Speter                                  "Type 'svnrdump help %s' for usage.\n"),
1026251881Speter                                subcommand->name, optstr, subcommand->name));
1027299742Sdim          *exit_code = EXIT_FAILURE;
1028299742Sdim          return SVN_NO_ERROR;
1029251881Speter        }
1030251881Speter    }
1031251881Speter
1032299742Sdim  if (strcmp(subcommand->name, "--version") == 0)
1033251881Speter    {
1034299742Sdim      SVN_ERR(version(argv[0], opt_baton->quiet, pool));
1035299742Sdim      return SVN_NO_ERROR;
1036251881Speter    }
1037251881Speter
1038299742Sdim  if (strcmp(subcommand->name, "help") == 0)
1039251881Speter    {
1040299742Sdim      SVN_ERR(help_cmd(os, opt_baton, pool));
1041299742Sdim      return SVN_NO_ERROR;
1042251881Speter    }
1043251881Speter
1044299742Sdim  /* --trust-* can only be used with --non-interactive */
1045299742Sdim  if (!non_interactive)
1046251881Speter    {
1047299742Sdim      if (trust_unknown_ca || trust_cn_mismatch || trust_expired
1048299742Sdim          || trust_not_yet_valid || trust_other_failure)
1049299742Sdim        return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
1050299742Sdim                                _("--trust-server-cert-failures requires "
1051299742Sdim                                  "--non-interactive"));
1052251881Speter    }
1053251881Speter
1054251881Speter  /* Expect one more non-option argument:  the repository URL. */
1055251881Speter  if (os->ind != os->argc - 1)
1056251881Speter    {
1057299742Sdim      SVN_ERR(usage(argv[0], pool));
1058299742Sdim      *exit_code = EXIT_FAILURE;
1059299742Sdim      return SVN_NO_ERROR;
1060251881Speter    }
1061251881Speter  else
1062251881Speter    {
1063251881Speter      const char *repos_url;
1064251881Speter
1065299742Sdim      SVN_ERR(svn_utf_cstring_to_utf8(&repos_url, os->argv[os->ind], pool));
1066251881Speter      if (! svn_path_is_url(repos_url))
1067251881Speter        {
1068299742Sdim          return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, 0,
1069299742Sdim                                   "Target '%s' is not a URL",
1070299742Sdim                                   repos_url);
1071251881Speter        }
1072251881Speter      opt_baton->url = svn_uri_canonicalize(repos_url, pool);
1073251881Speter    }
1074251881Speter
1075251881Speter  if (strcmp(subcommand->name, "load") == 0)
1076251881Speter    {
1077299742Sdim      /*
1078251881Speter       * By default (no --*-interactive options given), the 'load' subcommand
1079251881Speter       * is interactive unless username and password were provided on the
1080251881Speter       * command line. This allows prompting for auth creds to work without
1081251881Speter       * requiring users to remember to use --force-interactive.
1082251881Speter       * See issue #3913, "svnrdump load is not working in interactive mode".
1083251881Speter       */
1084251881Speter      if (!non_interactive && !force_interactive)
1085251881Speter        force_interactive = (username == NULL || password == NULL);
1086251881Speter    }
1087251881Speter
1088251881Speter  non_interactive = !svn_cmdline__be_interactive(non_interactive,
1089251881Speter                                                 force_interactive);
1090251881Speter
1091299742Sdim  SVN_ERR(init_client_context(&(opt_baton->ctx),
1092299742Sdim                              non_interactive,
1093299742Sdim                              username,
1094299742Sdim                              password,
1095299742Sdim                              config_dir,
1096299742Sdim                              opt_baton->url,
1097299742Sdim                              no_auth_cache,
1098299742Sdim                              trust_unknown_ca,
1099299742Sdim                              trust_cn_mismatch,
1100299742Sdim                              trust_expired,
1101299742Sdim                              trust_not_yet_valid,
1102299742Sdim                              trust_other_failure,
1103299742Sdim                              config_options,
1104299742Sdim                              pool));
1105251881Speter
1106251881Speter  err = svn_client_open_ra_session2(&(opt_baton->session),
1107251881Speter                                    opt_baton->url, NULL,
1108251881Speter                                    opt_baton->ctx, pool, pool);
1109251881Speter
1110251881Speter  /* Have sane opt_baton->start_revision and end_revision defaults if
1111251881Speter     unspecified.  */
1112251881Speter  if (!err)
1113251881Speter    err = svn_ra_get_latest_revnum(opt_baton->session, &latest_revision, pool);
1114251881Speter
1115251881Speter  /* Make sure any provided revisions make sense. */
1116251881Speter  if (!err)
1117251881Speter    err = validate_and_resolve_revisions(opt_baton, latest_revision, pool);
1118251881Speter
1119251881Speter  /* Dispatch the subcommand */
1120251881Speter  if (!err)
1121251881Speter    err = (*subcommand->cmd_func)(os, opt_baton, pool);
1122251881Speter
1123251881Speter  if (err && err->apr_err == SVN_ERR_AUTHN_FAILED && non_interactive)
1124251881Speter    {
1125299742Sdim      return svn_error_quick_wrap(err,
1126299742Sdim                                  _("Authentication failed and interactive"
1127299742Sdim                                    " prompting is disabled; see the"
1128299742Sdim                                    " --force-interactive option"));
1129251881Speter    }
1130299742Sdim  else if (err)
1131299742Sdim    return err;
1132299742Sdim  else
1133299742Sdim    return SVN_NO_ERROR;
1134299742Sdim}
1135251881Speter
1136299742Sdimint
1137299742Sdimmain(int argc, const char *argv[])
1138299742Sdim{
1139299742Sdim  apr_pool_t *pool;
1140299742Sdim  int exit_code = EXIT_SUCCESS;
1141299742Sdim  svn_error_t *err;
1142251881Speter
1143299742Sdim  /* Initialize the app. */
1144299742Sdim  if (svn_cmdline_init("svnrdump", stderr) != EXIT_SUCCESS)
1145299742Sdim    return EXIT_FAILURE;
1146299742Sdim
1147299742Sdim  /* Create our top-level pool.  Use a separate mutexless allocator,
1148299742Sdim   * given this application is single threaded.
1149299742Sdim   */
1150299742Sdim  pool = apr_allocator_owner_get(svn_pool_create_allocator(FALSE));
1151299742Sdim
1152299742Sdim  err = sub_main(&exit_code, argc, argv, pool);
1153299742Sdim
1154299742Sdim  /* Flush stdout and report if it fails. It would be flushed on exit anyway
1155299742Sdim     but this makes sure that output is not silently lost if it fails. */
1156299742Sdim  err = svn_error_compose_create(err, svn_cmdline_fflush(stdout));
1157299742Sdim
1158299742Sdim  if (err)
1159299742Sdim    {
1160299742Sdim      exit_code = EXIT_FAILURE;
1161299742Sdim      svn_cmdline_handle_exit_error(err, NULL, "svnrdump: ");
1162299742Sdim    }
1163299742Sdim
1164251881Speter  svn_pool_destroy(pool);
1165299742Sdim  return exit_code;
1166251881Speter}
1167