Sync with upstream
[svnrdump.git] / svnrdump.c
blobdb5c3533ea69836d7b8f97d43390fd77d611c1da
1 /*
2 * svnrdump.c: Produce a dumpfile of a local or remote repository
3 * without touching the filesystem, but for temporary files.
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
14 * http://www.apache.org/licenses/LICENSE-2.0
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 * ====================================================================
25 #include <apr_signal.h>
27 #include "svn_pools.h"
28 #include "svn_cmdline.h"
29 #include "svn_client.h"
30 #include "svn_hash.h"
31 #include "svn_ra.h"
32 #include "svn_repos.h"
33 #include "svn_path.h"
34 #include "svn_utf.h"
35 #include "svn_string.h"
36 #include "svn_props.h"
37 #include "svn_dirent_uri.h"
39 #include "svn17_compat.h"
40 #include "dump_editor.h"
41 #include "load_editor.h"
45 /*** Cancellation ***/
47 /* A flag to see if we've been cancelled by the client or not. */
48 static volatile sig_atomic_t cancelled = FALSE;
50 /* A signal handler to support cancellation. */
51 static void
52 signal_handler(int signum)
54 apr_signal(signum, SIG_IGN);
55 cancelled = TRUE;
58 /* Our cancellation callback. */
59 static svn_error_t *
60 check_cancel(void *baton)
62 if (cancelled)
63 return svn_error_create(SVN_ERR_CANCELLED, NULL, _("Caught signal"));
64 else
65 return SVN_NO_ERROR;
71 static svn_opt_subcommand_t dump_cmd, load_cmd;
73 enum svn_svnrdump__longopt_t
75 opt_config_dir = SVN_OPT_FIRST_LONGOPT_ID,
76 opt_auth_username,
77 opt_auth_password,
78 opt_non_interactive,
79 opt_auth_nocache,
80 opt_version,
81 opt_config_option,
84 static const svn_opt_subcommand_desc2_t svnrdump__cmd_table[] =
86 { "dump", dump_cmd, { 0 },
87 N_("usage: svnrdump dump URL [-r LOWER[:UPPER]]\n\n"
88 "Dump revisions LOWER to UPPER of repository at remote URL "
89 "to stdout in a 'dumpfile' portable format.\n"
90 "If only LOWER is given, dump that one revision.\n"),
91 { 0 } },
92 { "load", load_cmd, { 0 },
93 N_("usage: svnrdump load URL\n\n"
94 "Load a 'dumpfile' given on stdin to a repository "
95 "at remote URL.\n"),
96 { 0 } },
97 { "help", 0, { "?", "h" },
98 N_("usage: svnrdump help [SUBCOMMAND...]\n\n"
99 "Describe the usage of this program or its subcommands.\n"),
100 { 0 } },
101 { NULL, NULL, { 0 }, NULL, { 0 } }
104 static const apr_getopt_option_t svnrdump__options[] =
106 {"revision", 'r', 1,
107 N_("specify revision number ARG (or X:Y range)")},
108 {"quiet", 'q', 0,
109 N_("no progress (only errors) to stderr")},
110 {"config-dir", opt_config_dir, 1,
111 N_("read user configuration files from directory ARG")},
112 {"username", opt_auth_username, 1,
113 N_("specify a username ARG")},
114 {"password", opt_auth_password, 1,
115 N_("specify a password ARG")},
116 {"non-interactive", opt_non_interactive, 0,
117 N_("do no interactive prompting")},
118 {"no-auth-cache", opt_auth_nocache, 0,
119 N_("do not cache authentication tokens")},
120 {"help", 'h', 0,
121 N_("display this help")},
122 {"version", opt_version, 0,
123 N_("show program version information")},
124 {"config-option", opt_config_option, 1,
125 N_("set user configuration option in the format:\n"
127 " FILE:SECTION:OPTION=[VALUE]\n"
129 "For example:\n"
131 " servers:global:http-library=serf")},
132 {0, 0, 0, 0}
135 /* Baton for the RA replay session. */
136 struct replay_baton {
137 /* The editor producing diffs. */
138 const svn_delta_editor_t *editor;
140 /* Baton for the editor. */
141 void *edit_baton;
143 /* Whether to be quiet. */
144 svn_boolean_t quiet;
147 /* Option set */
148 typedef struct opt_baton_t {
149 svn_ra_session_t *session;
150 const char *url;
151 svn_revnum_t start_revision;
152 svn_revnum_t end_revision;
153 svn_boolean_t quiet;
154 } opt_baton_t;
156 /* Print dumpstream-formatted information about REVISION.
157 * Implements the `svn_ra_replay_revstart_callback_t' interface.
159 static svn_error_t *
160 replay_revstart(svn_revnum_t revision,
161 void *replay_baton,
162 const svn_delta_editor_t **editor,
163 void **edit_baton,
164 apr_hash_t *rev_props,
165 apr_pool_t *pool)
167 struct replay_baton *rb = replay_baton;
168 svn_stringbuf_t *propstring;
169 svn_stream_t *stdout_stream;
170 svn_stream_t *revprop_stream;
172 svn_stream_for_stdout(&stdout_stream, pool);
174 /* Revision-number: 19 */
175 SVN_ERR(svn_stream_printf(stdout_stream, pool,
176 SVN_REPOS_DUMPFILE_REVISION_NUMBER
177 ": %ld\n", revision));
178 SVN_ERR(normalize_props(rev_props, pool));
179 propstring = svn_stringbuf_create_ensure(0, pool);
180 revprop_stream = svn_stream_from_stringbuf(propstring, pool);
181 SVN_ERR(svn_hash_write2(rev_props, revprop_stream, "PROPS-END", pool));
182 SVN_ERR(svn_stream_close(revprop_stream));
184 /* Prop-content-length: 13 */
185 SVN_ERR(svn_stream_printf(stdout_stream, pool,
186 SVN_REPOS_DUMPFILE_PROP_CONTENT_LENGTH
187 ": %" APR_SIZE_T_FMT "\n", propstring->len));
189 /* Content-length: 29 */
190 SVN_ERR(svn_stream_printf(stdout_stream, pool,
191 SVN_REPOS_DUMPFILE_CONTENT_LENGTH
192 ": %" APR_SIZE_T_FMT "\n\n", propstring->len));
194 /* Property data. */
195 SVN_ERR(svn_stream_write(stdout_stream, propstring->data,
196 &(propstring->len)));
198 SVN_ERR(svn_stream_printf(stdout_stream, pool, "\n"));
199 SVN_ERR(svn_stream_close(stdout_stream));
201 /* Extract editor and editor_baton from the replay_baton and
202 set them so that the editor callbacks can use them. */
203 *editor = rb->editor;
204 *edit_baton = rb->edit_baton;
206 return SVN_NO_ERROR;
209 /* Print progress information about the dump of REVISION.
210 Implements the `svn_ra_replay_revfinish_callback_t' interface. */
211 static svn_error_t *
212 replay_revend(svn_revnum_t revision,
213 void *replay_baton,
214 const svn_delta_editor_t *editor,
215 void *edit_baton,
216 apr_hash_t *rev_props,
217 apr_pool_t *pool)
219 /* No resources left to free. */
220 struct replay_baton *rb = replay_baton;
221 if (! rb->quiet)
222 svn_cmdline_fprintf(stderr, pool, "* Dumped revision %lu.\n", revision);
223 return SVN_NO_ERROR;
226 /* Set *SESSION to a new RA session opened to URL. Allocate *SESSION
227 * and related data structures in POOL. Use CONFIG_DIR and pass
228 * USERNAME, PASSWORD, CONFIG_DIR and NO_AUTH_CACHE to initialize the
229 * authorization baton. CONFIG_OPTIONS (if not NULL) is a list of
230 * configuration overrides.
232 static svn_error_t *
233 open_connection(svn_ra_session_t **session,
234 const char *url,
235 svn_boolean_t non_interactive,
236 const char *username,
237 const char *password,
238 const char *config_dir,
239 svn_boolean_t no_auth_cache,
240 apr_array_header_t *config_options,
241 apr_pool_t *pool)
243 svn_client_ctx_t *ctx = NULL;
244 svn_config_t *cfg_config;
246 SVN_ERR(svn_ra_initialize(pool));
248 SVN_ERR(svn_config_ensure(config_dir, pool));
249 SVN_ERR(svn_client_create_context(&ctx, pool));
251 SVN_ERR(svn_config_get_config(&(ctx->config), config_dir, pool));
253 if (config_options)
254 SVN_ERR(svn_cmdline__apply_config_options(ctx->config, config_options,
255 "svnrdump: ", "--config-option"));
257 cfg_config = apr_hash_get(ctx->config, SVN_CONFIG_CATEGORY_CONFIG,
258 APR_HASH_KEY_STRING);
260 /* Set up our cancellation support. */
261 ctx->cancel_func = check_cancel;
263 /* Default authentication providers for non-interactive use */
264 SVN_ERR(svn_cmdline_create_auth_baton(&(ctx->auth_baton), non_interactive,
265 username, password, config_dir,
266 no_auth_cache, FALSE, cfg_config,
267 ctx->cancel_func, ctx->cancel_baton,
268 pool));
269 SVN_ERR(svn_client_open_ra_session(session, url, ctx, pool));
270 return SVN_NO_ERROR;
273 /* Replay revisions START_REVISION thru END_REVISION (inclusive) of
274 * the repository located at URL, using callbacks which generate
275 * Subversion repository dumpstreams describing the changes made in
276 * those revisions. If QUIET is set, don't generate progress
277 * messages.
279 static svn_error_t *
280 replay_revisions(svn_ra_session_t *session,
281 const char *url,
282 svn_revnum_t start_revision,
283 svn_revnum_t end_revision,
284 svn_boolean_t quiet,
285 apr_pool_t *pool)
287 const svn_delta_editor_t *dump_editor;
288 struct replay_baton *replay_baton;
289 void *dump_baton;
290 const char *uuid;
291 svn_stream_t *stdout_stream;
293 SVN_ERR(svn_stream_for_stdout(&stdout_stream, pool));
295 SVN_ERR(get_dump_editor(&dump_editor, &dump_baton, stdout_stream,
296 check_cancel, NULL, pool));
298 replay_baton = apr_pcalloc(pool, sizeof(*replay_baton));
299 replay_baton->editor = dump_editor;
300 replay_baton->edit_baton = dump_baton;
301 replay_baton->quiet = quiet;
303 /* Write the magic header and UUID */
304 SVN_ERR(svn_stream_printf(stdout_stream, pool,
305 SVN_REPOS_DUMPFILE_MAGIC_HEADER ": %d\n\n",
306 SVN_REPOS_DUMPFILE_FORMAT_VERSION));
307 SVN_ERR(svn_ra_get_uuid2(session, &uuid, pool));
308 SVN_ERR(svn_stream_printf(stdout_stream, pool,
309 SVN_REPOS_DUMPFILE_UUID ": %s\n\n", uuid));
311 /* Fake revision 0 if necessary */
312 if (start_revision == 0)
314 apr_hash_t *prophash;
315 svn_stringbuf_t *propstring;
316 svn_stream_t *propstream;
317 SVN_ERR(svn_stream_printf(stdout_stream, pool,
318 SVN_REPOS_DUMPFILE_REVISION_NUMBER
319 ": %ld\n", start_revision));
321 prophash = apr_hash_make(pool);
322 propstring = svn_stringbuf_create("", pool);
324 SVN_ERR(svn_ra_rev_proplist(session, start_revision,
325 &prophash, pool));
327 propstream = svn_stream_from_stringbuf(propstring, pool);
328 SVN_ERR(svn_hash_write2(prophash, propstream, "PROPS-END", pool));
329 SVN_ERR(svn_stream_close(propstream));
331 /* Property-content-length: 14; Content-length: 14 */
332 SVN_ERR(svn_stream_printf(stdout_stream, pool,
333 SVN_REPOS_DUMPFILE_PROP_CONTENT_LENGTH
334 ": %" APR_SIZE_T_FMT "\n",
335 propstring->len));
336 SVN_ERR(svn_stream_printf(stdout_stream, pool,
337 SVN_REPOS_DUMPFILE_CONTENT_LENGTH
338 ": %" APR_SIZE_T_FMT "\n\n",
339 propstring->len));
340 /* The properties */
341 SVN_ERR(svn_stream_write(stdout_stream, propstring->data,
342 &(propstring->len)));
343 SVN_ERR(svn_stream_printf(stdout_stream, pool, "\n"));
344 if (! quiet)
345 svn_cmdline_fprintf(stderr, pool, "* Dumped revision %lu.\n",
346 start_revision);
348 start_revision++;
351 SVN_ERR(svn_ra_replay_range(session, start_revision, end_revision,
352 0, TRUE, replay_revstart, replay_revend,
353 replay_baton, pool));
354 SVN_ERR(svn_stream_close(stdout_stream));
355 return SVN_NO_ERROR;
358 /* Read a dumpstream from stdin, and use it to feed a loader capable
359 * of transmitting that information to the repository located at URL
360 * (to which SESSION has been opened).
362 static svn_error_t *
363 load_revisions(svn_ra_session_t *session,
364 const char *url,
365 svn_boolean_t quiet,
366 apr_pool_t *pool)
368 apr_file_t *stdin_file;
369 svn_stream_t *stdin_stream;
370 const svn_repos_parse_fns2_t *parser;
371 void *parse_baton;
373 apr_file_open_stdin(&stdin_file, pool);
374 stdin_stream = svn_stream_from_aprfile2(stdin_file, FALSE, pool);
376 SVN_ERR(get_dumpstream_loader(&parser, &parse_baton, session, pool));
377 SVN_ERR(drive_dumpstream_loader(stdin_stream, parser, parse_baton,
378 session, check_cancel, NULL, pool));
380 svn_stream_close(stdin_stream);
382 return SVN_NO_ERROR;
385 /* Return a program name for this program, the basename of the path
386 * represented by PROGNAME if not NULL; use "svnrdump" otherwise.
388 static const char *
389 ensure_appname(const char *progname,
390 apr_pool_t *pool)
392 if (!progname)
393 return "svnrdump";
395 return svn_dirent_basename(svn_dirent_internal_style(progname, pool), NULL);
398 /* Print a simple usage string. */
399 static svn_error_t *
400 usage(const char *progname,
401 apr_pool_t *pool)
403 return svn_cmdline_fprintf(stderr, pool,
404 _("Type '%s help' for usage.\n"),
405 ensure_appname(progname, pool));
408 /* Print information about the version of this program and dependent
409 * modules.
411 static svn_error_t *
412 version(const char *progname,
413 apr_pool_t *pool)
415 svn_stringbuf_t *version_footer =
416 svn_stringbuf_create(_("The following repository access (RA) modules "
417 "are available:\n\n"),
418 pool);
420 SVN_ERR(svn_ra_print_modules(version_footer, pool));
421 return svn_opt_print_help3(NULL, ensure_appname(progname, pool),
422 TRUE, FALSE, version_footer->data,
423 NULL, NULL, NULL, NULL, NULL, pool);
427 /* A statement macro, similar to @c SVN_ERR, but returns an integer.
428 * Evaluate @a expr. If it yields an error, handle that error and
429 * return @c EXIT_FAILURE.
431 #define SVNRDUMP_ERR(expr) \
432 do \
434 svn_error_t *svn_err__temp = (expr); \
435 if (svn_err__temp) \
437 svn_handle_error2(svn_err__temp, stderr, FALSE, "svnrdump: "); \
438 svn_error_clear(svn_err__temp); \
439 return EXIT_FAILURE; \
442 while (0)
444 /* Handle the "dump" subcommand. Implements `svn_opt_subcommand_t'. */
445 static svn_error_t *
446 dump_cmd(apr_getopt_t *os,
447 void *baton,
448 apr_pool_t *pool)
450 opt_baton_t *opt_baton = baton;
451 return replay_revisions(opt_baton->session, opt_baton->url,
452 opt_baton->start_revision, opt_baton->end_revision,
453 opt_baton->quiet, pool);
456 /* Handle the "load" subcommand. Implements `svn_opt_subcommand_t'. */
457 static svn_error_t *
458 load_cmd(apr_getopt_t *os,
459 void *baton,
460 apr_pool_t *pool)
462 opt_baton_t *opt_baton = baton;
463 return load_revisions(opt_baton->session, opt_baton->url,
464 opt_baton->quiet, pool);
467 /* Handle the "help" subcommand. Implements `svn_opt_subcommand_t'. */
468 static svn_error_t *
469 help_cmd(apr_getopt_t *os,
470 void *baton,
471 apr_pool_t *pool)
473 const char *header =
474 _("general usage: svnrdump SUBCOMMAND URL [-r LOWER[:UPPER]]\n"
475 "Type 'svnrdump help <subcommand>' for help on a specific subcommand.\n"
476 "\n"
477 "Available subcommands:\n");
479 return svn_opt_print_help3(os, "svnrdump", FALSE, FALSE, NULL, header,
480 svnrdump__cmd_table, svnrdump__options, NULL,
481 NULL, pool);
485 main(int argc, const char **argv)
487 const svn_opt_subcommand_desc2_t *subcommand = NULL;
488 opt_baton_t *opt_baton;
489 char *revision_cut = NULL;
490 svn_revnum_t latest_revision = svn_opt_revision_unspecified;
491 apr_pool_t *pool = NULL;
492 const char *config_dir = NULL;
493 const char *username = NULL;
494 const char *password = NULL;
495 svn_boolean_t no_auth_cache = FALSE;
496 svn_boolean_t non_interactive = FALSE;
497 apr_array_header_t *config_options = NULL;
498 apr_getopt_t *os;
499 const char *first_arg;
501 if (svn_cmdline_init ("svnrdump", stderr) != EXIT_SUCCESS)
502 return EXIT_FAILURE;
504 pool = svn_pool_create(NULL);
505 opt_baton = apr_pcalloc(pool, sizeof(*opt_baton));
506 opt_baton->start_revision = svn_opt_revision_unspecified;
507 opt_baton->end_revision = svn_opt_revision_unspecified;
508 opt_baton->url = NULL;
510 SVNRDUMP_ERR(svn_cmdline__getopt_init(&os, argc, argv, pool));
512 os->interleave = TRUE; /* Options and arguments can be interleaved */
514 /* Set up our cancellation support. */
515 apr_signal(SIGINT, signal_handler);
516 #ifdef SIGBREAK
517 /* SIGBREAK is a Win32 specific signal generated by ctrl-break. */
518 apr_signal(SIGBREAK, signal_handler);
519 #endif
520 #ifdef SIGHUP
521 apr_signal(SIGHUP, signal_handler);
522 #endif
523 #ifdef SIGTERM
524 apr_signal(SIGTERM, signal_handler);
525 #endif
526 #ifdef SIGPIPE
527 /* Disable SIGPIPE generation for the platforms that have it. */
528 apr_signal(SIGPIPE, SIG_IGN);
529 #endif
530 #ifdef SIGXFSZ
531 /* Disable SIGXFSZ generation for the platforms that have it, otherwise
532 * working with large files when compiled against an APR that doesn't have
533 * large file support will crash the program, which is uncool. */
534 apr_signal(SIGXFSZ, SIG_IGN);
535 #endif
537 while (1)
539 int opt;
540 const char *opt_arg;
541 apr_status_t status = apr_getopt_long(os, svnrdump__options, &opt,
542 &opt_arg);
544 if (APR_STATUS_IS_EOF(status))
545 break;
546 if (status != APR_SUCCESS)
548 SVNRDUMP_ERR(usage(argv[0], pool));
549 exit(EXIT_FAILURE);
552 switch(opt)
554 case 'r':
556 revision_cut = strchr(opt_arg, ':');
557 if (revision_cut)
559 opt_baton->start_revision =
560 (svn_revnum_t)strtoul(opt_arg, &revision_cut, 10);
561 opt_baton->end_revision =
562 (svn_revnum_t)strtoul(revision_cut + 1, NULL, 10);
564 else
566 opt_baton->start_revision =
567 (svn_revnum_t)strtoul(opt_arg, NULL, 10);
568 opt_baton->end_revision = opt_baton->start_revision;
571 break;
572 case 'q':
573 opt_baton->quiet = TRUE;
574 break;
575 case opt_config_dir:
576 config_dir = opt_arg;
577 break;
578 case opt_version:
579 SVNRDUMP_ERR(version(argv[0], pool));
580 exit(EXIT_SUCCESS);
581 break;
582 case 'h':
583 SVNRDUMP_ERR(help_cmd(os, opt_baton, pool));
584 exit(EXIT_SUCCESS);
585 break;
586 case opt_auth_username:
587 SVNRDUMP_ERR(svn_utf_cstring_to_utf8(&username, opt_arg, pool));
588 break;
589 case opt_auth_password:
590 SVNRDUMP_ERR(svn_utf_cstring_to_utf8(&password, opt_arg, pool));
591 break;
592 case opt_auth_nocache:
593 no_auth_cache = TRUE;
594 break;
595 case opt_non_interactive:
596 non_interactive = TRUE;
597 break;
598 case opt_config_option:
599 if (!config_options)
600 config_options =
601 apr_array_make(pool, 1,
602 sizeof(svn_cmdline__config_argument_t*));
604 SVNRDUMP_ERR(svn_utf_cstring_to_utf8(&opt_arg, opt_arg, pool));
605 SVNRDUMP_ERR(svn_cmdline__parse_config_option(config_options,
606 opt_arg, pool));
610 if (os->ind >= os->argc)
612 svn_error_clear(svn_cmdline_fprintf(stderr, pool,
613 _("Subcommand argument required\n")));
614 SVNRDUMP_ERR(help_cmd(NULL, NULL, pool));
615 svn_pool_destroy(pool);
616 exit(EXIT_FAILURE);
619 first_arg = os->argv[os->ind++];
621 subcommand = svn_opt_get_canonical_subcommand2(svnrdump__cmd_table,
622 first_arg);
624 if (subcommand == NULL)
626 const char *first_arg_utf8;
627 svn_error_t *err = svn_utf_cstring_to_utf8(&first_arg_utf8,
628 first_arg, pool);
629 if (err)
630 return svn_cmdline_handle_exit_error(err, pool, "svnrdump: ");
631 svn_error_clear(svn_cmdline_fprintf(stderr, pool,
632 _("Unknown command: '%s'\n"),
633 first_arg_utf8));
634 SVNRDUMP_ERR(help_cmd(NULL, NULL, pool));
635 svn_pool_destroy(pool);
636 exit(EXIT_FAILURE);
639 if (subcommand && strcmp(subcommand->name, "help") == 0)
641 SVNRDUMP_ERR(help_cmd(os, opt_baton, pool));
642 exit(EXIT_SUCCESS);
645 /* Only continue if the only not option argument is a url */
646 if ((os->ind != os->argc-1)
647 || !svn_path_is_url(os->argv[os->ind]))
649 SVNRDUMP_ERR(usage(argv[0], pool));
650 exit(EXIT_FAILURE);
653 SVNRDUMP_ERR(svn_utf_cstring_to_utf8(&(opt_baton->url),
654 os->argv[os->ind], pool));
656 opt_baton->url = svn_uri_canonicalize(os->argv[os->ind], pool);
658 SVNRDUMP_ERR(open_connection(&(opt_baton->session),
659 opt_baton->url,
660 non_interactive,
661 username,
662 password,
663 config_dir,
664 no_auth_cache,
665 config_options,
666 pool));
668 /* Have sane opt_baton->start_revision and end_revision defaults if
669 unspecified. */
670 SVNRDUMP_ERR(svn_ra_get_latest_revnum(opt_baton->session,
671 &latest_revision, pool));
672 if (opt_baton->start_revision == svn_opt_revision_unspecified)
673 opt_baton->start_revision = 0;
674 if (opt_baton->end_revision == svn_opt_revision_unspecified)
675 opt_baton->end_revision = latest_revision;
676 if (opt_baton->end_revision > latest_revision)
678 SVN_INT_ERR(svn_cmdline_fprintf(stderr, pool,
679 _("Revision %ld does not exist.\n"),
680 opt_baton->end_revision));
681 exit(EXIT_FAILURE);
683 if (opt_baton->end_revision < opt_baton->start_revision)
685 SVN_INT_ERR(svn_cmdline_fprintf(stderr, pool,
686 _("LOWER cannot be greater "
687 "than UPPER.\n")));
688 exit(EXIT_FAILURE);
691 /* Dispatch the subcommand */
692 SVNRDUMP_ERR((*subcommand->cmd_func)(os, opt_baton, pool));
694 svn_pool_destroy(pool);
696 return EXIT_SUCCESS;