Add a little more to the svn_rangelist_intersect test to test the
[svn.git] / subversion / svnadmin / main.c
blobdb7d444206cd4016d93fe1124512f21c5f9d9a26
1 /*
2 * main.c: Subversion server administration tool.
4 * ====================================================================
5 * Copyright (c) 2000-2007 CollabNet. All rights reserved.
7 * This software is licensed as described in the file COPYING, which
8 * you should have received as part of this distribution. The terms
9 * are also available at http://subversion.tigris.org/license-1.html.
10 * If newer versions of this license are posted there, you may use a
11 * newer version instead, at your option.
13 * This software consists of voluntary contributions made by many
14 * individuals. For exact contribution history, see the revision
15 * history and logs, available at http://subversion.tigris.org/.
16 * ====================================================================
20 #include <apr_file_io.h>
21 #include <apr_signal.h>
23 #include "svn_pools.h"
24 #include "svn_cmdline.h"
25 #include "svn_error.h"
26 #include "svn_opt.h"
27 #include "svn_utf.h"
28 #include "svn_subst.h"
29 #include "svn_path.h"
30 #include "svn_config.h"
31 #include "svn_repos.h"
32 #include "svn_fs.h"
33 #include "svn_version.h"
34 #include "svn_props.h"
35 #include "svn_time.h"
36 #include "svn_user.h"
38 #include "svn_private_config.h"
41 /*** Code. ***/
43 /* A flag to see if we've been cancelled by the client or not. */
44 static volatile sig_atomic_t cancelled = FALSE;
46 /* A signal handler to support cancellation. */
47 static void
48 signal_handler(int signum)
50 apr_signal(signum, SIG_IGN);
51 cancelled = TRUE;
55 /* A helper to set up the cancellation signal handlers. */
56 static void
57 setup_cancellation_signals(void (*handler)(int signum))
59 apr_signal(SIGINT, handler);
60 #ifdef SIGBREAK
61 /* SIGBREAK is a Win32 specific signal generated by ctrl-break. */
62 apr_signal(SIGBREAK, handler);
63 #endif
64 #ifdef SIGHUP
65 apr_signal(SIGHUP, handler);
66 #endif
67 #ifdef SIGTERM
68 apr_signal(SIGTERM, handler);
69 #endif
73 /* Our cancellation callback. */
74 static svn_error_t *
75 check_cancel(void *baton)
77 if (cancelled)
78 return svn_error_create(SVN_ERR_CANCELLED, NULL, _("Caught signal"));
79 else
80 return SVN_NO_ERROR;
84 /* Helper to open stdio streams */
85 static svn_error_t *
86 create_stdio_stream(svn_stream_t **stream,
87 APR_DECLARE(apr_status_t) open_fn(apr_file_t **,
88 apr_pool_t *),
89 apr_pool_t *pool)
91 apr_file_t *stdio_file;
92 apr_status_t apr_err = open_fn(&stdio_file, pool);
94 if (apr_err)
95 return svn_error_wrap_apr(apr_err, _("Can't open stdio file"));
97 *stream = svn_stream_from_aprfile(stdio_file, pool);
98 return SVN_NO_ERROR;
102 /* Helper to parse local repository path. Try parsing next parameter
103 * of OS as a local path to repository. If successfull *REPOS_PATH
104 * will contain internal style path to the repository.
106 static svn_error_t *
107 parse_local_repos_path(apr_getopt_t *os,
108 const char ** repos_path,
109 apr_pool_t *pool)
111 *repos_path = NULL;
113 /* Check to see if there is one more parameter. */
114 if (os->ind < os->argc)
116 const char * path = os->argv[os->ind++];
117 SVN_ERR(svn_utf_cstring_to_utf8(repos_path, path, pool));
118 *repos_path = svn_path_internal_style(*repos_path, pool);
121 if (*repos_path == NULL)
123 return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
124 _("Repository argument required"));
126 else if (svn_path_is_url(*repos_path))
128 return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
129 _("'%s' is an URL when it should be a path"),
130 *repos_path);
133 return SVN_NO_ERROR;
137 /* Custom filesystem warning function. */
138 static void
139 warning_func(void *baton,
140 svn_error_t *err)
142 if (! err)
143 return;
144 svn_handle_error2(err, stderr, FALSE, "svnadmin: ");
148 /* Helper to open a repository and set a warning func (so we don't
149 * SEGFAULT when libsvn_fs's default handler gets run). */
150 static svn_error_t *
151 open_repos(svn_repos_t **repos,
152 const char *path,
153 apr_pool_t *pool)
155 SVN_ERR(svn_repos_open(repos, path, pool));
156 svn_fs_set_warning_func(svn_repos_fs(*repos), warning_func, NULL);
157 return SVN_NO_ERROR;
161 /* Version compatibility check */
162 static svn_error_t *
163 check_lib_versions(void)
165 static const svn_version_checklist_t checklist[] =
167 { "svn_subr", svn_subr_version },
168 { "svn_repos", svn_repos_version },
169 { "svn_fs", svn_fs_version },
170 { "svn_delta", svn_delta_version },
171 { NULL, NULL }
174 SVN_VERSION_DEFINE(my_version);
175 return svn_ver_check_list(&my_version, checklist);
180 /** Subcommands. **/
182 static svn_opt_subcommand_t
183 subcommand_crashtest,
184 subcommand_create,
185 subcommand_deltify,
186 subcommand_dump,
187 subcommand_help,
188 subcommand_hotcopy,
189 subcommand_load,
190 subcommand_list_dblogs,
191 subcommand_list_unused_dblogs,
192 subcommand_lslocks,
193 subcommand_lstxns,
194 subcommand_recover,
195 subcommand_rmlocks,
196 subcommand_rmtxns,
197 subcommand_setlog,
198 subcommand_setrevprop,
199 subcommand_setuuid,
200 subcommand_verify;
202 enum
204 svnadmin__version = SVN_OPT_FIRST_LONGOPT_ID,
205 svnadmin__incremental,
206 svnadmin__deltas,
207 svnadmin__ignore_uuid,
208 svnadmin__force_uuid,
209 svnadmin__fs_type,
210 svnadmin__parent_dir,
211 svnadmin__bdb_txn_nosync,
212 svnadmin__bdb_log_keep,
213 svnadmin__config_dir,
214 svnadmin__bypass_hooks,
215 svnadmin__use_pre_commit_hook,
216 svnadmin__use_post_commit_hook,
217 svnadmin__use_pre_revprop_change_hook,
218 svnadmin__use_post_revprop_change_hook,
219 svnadmin__clean_logs,
220 svnadmin__wait,
221 svnadmin__pre_1_4_compatible,
222 svnadmin__pre_1_5_compatible
225 /* Option codes and descriptions.
227 * The entire list must be terminated with an entry of nulls.
229 static const apr_getopt_option_t options_table[] =
231 {"help", 'h', 0,
232 N_("show help on a subcommand")},
234 {NULL, '?', 0,
235 N_("show help on a subcommand")},
237 {"version", svnadmin__version, 0,
238 N_("show program version information")},
240 {"revision", 'r', 1,
241 N_("specify revision number ARG (or X:Y range)")},
243 {"incremental", svnadmin__incremental, 0,
244 N_("dump incrementally")},
246 {"deltas", svnadmin__deltas, 0,
247 N_("use deltas in dump output")},
249 {"bypass-hooks", svnadmin__bypass_hooks, 0,
250 N_("bypass the repository hook system")},
252 {"quiet", 'q', 0,
253 N_("no progress (only errors) to stderr")},
255 {"ignore-uuid", svnadmin__ignore_uuid, 0,
256 N_("ignore any repos UUID found in the stream")},
258 {"force-uuid", svnadmin__force_uuid, 0,
259 N_("set repos UUID to that found in stream, if any")},
261 {"fs-type", svnadmin__fs_type, 1,
262 N_("type of repository: 'fsfs' (default) or 'bdb'")},
264 {"parent-dir", svnadmin__parent_dir, 1,
265 N_("load at specified directory in repository")},
267 {"bdb-txn-nosync", svnadmin__bdb_txn_nosync, 0,
268 N_("disable fsync at transaction commit [Berkeley DB]")},
270 {"bdb-log-keep", svnadmin__bdb_log_keep, 0,
271 N_("disable automatic log file removal [Berkeley DB]")},
273 {"config-dir", svnadmin__config_dir, 1,
274 N_("read user configuration files from directory ARG")},
276 {"clean-logs", svnadmin__clean_logs, 0,
277 N_("remove redundant Berkeley DB log files\n"
278 " from source repository [Berkeley DB]")},
280 {"use-pre-commit-hook", svnadmin__use_pre_commit_hook, 0,
281 N_("call pre-commit hook before committing revisions")},
283 {"use-post-commit-hook", svnadmin__use_post_commit_hook, 0,
284 N_("call post-commit hook after committing revisions")},
286 {"use-pre-revprop-change-hook", svnadmin__use_pre_revprop_change_hook, 0,
287 N_("call hook before changing revision property")},
289 {"use-post-revprop-change-hook", svnadmin__use_post_revprop_change_hook, 0,
290 N_("call hook after changing revision property")},
292 {"wait", svnadmin__wait, 0,
293 N_("wait instead of exit if the repository is in\n"
294 " use by another process")},
296 {"pre-1.4-compatible", svnadmin__pre_1_4_compatible, 0,
297 N_("use format compatible with Subversion versions\n"
298 " earlier than 1.4")},
300 {"pre-1.5-compatible", svnadmin__pre_1_5_compatible, 0,
301 N_("use format compatible with Subversion versions\n"
302 " earlier than 1.5")},
304 {NULL}
308 /* Array of available subcommands.
309 * The entire list must be terminated with an entry of nulls.
311 static const svn_opt_subcommand_desc_t cmd_table[] =
313 {"crashtest", subcommand_crashtest, {0}, N_
314 ("usage: svnadmin crashtest REPOS_PATH\n\n"
315 "Open the repository at REPOS_PATH, then abort, thus simulating\n"
316 "a process that crashes while holding an open repository handle.\n"),
317 {0} },
319 {"create", subcommand_create, {0}, N_
320 ("usage: svnadmin create REPOS_PATH\n\n"
321 "Create a new, empty repository at REPOS_PATH.\n"),
322 {svnadmin__bdb_txn_nosync, svnadmin__bdb_log_keep,
323 svnadmin__config_dir, svnadmin__fs_type, svnadmin__pre_1_4_compatible,
324 svnadmin__pre_1_5_compatible } },
326 {"deltify", subcommand_deltify, {0}, N_
327 ("usage: svnadmin deltify [-r LOWER[:UPPER]] REPOS_PATH\n\n"
328 "Run over the requested revision range, performing predecessor delti-\n"
329 "fication on the paths changed in those revisions. Deltification in\n"
330 "essence compresses the repository by only storing the differences or\n"
331 "delta from the preceding revision. If no revisions are specified,\n"
332 "this will simply deltify the HEAD revision.\n"),
333 {'r', 'q'} },
335 {"dump", subcommand_dump, {0}, N_
336 ("usage: svnadmin dump REPOS_PATH [-r LOWER[:UPPER]] [--incremental]\n\n"
337 "Dump the contents of filesystem to stdout in a 'dumpfile'\n"
338 "portable format, sending feedback to stderr. Dump revisions\n"
339 "LOWER rev through UPPER rev. If no revisions are given, dump all\n"
340 "revision trees. If only LOWER is given, dump that one revision tree.\n"
341 "If --incremental is passed, then the first revision dumped will be\n"
342 "a diff against the previous revision, instead of the usual fulltext.\n"),
343 {'r', svnadmin__incremental, svnadmin__deltas, 'q'} },
345 {"help", subcommand_help, {"?", "h"}, N_
346 ("usage: svnadmin help [SUBCOMMAND...]\n\n"
347 "Describe the usage of this program or its subcommands.\n"),
348 {0} },
350 {"hotcopy", subcommand_hotcopy, {0}, N_
351 ("usage: svnadmin hotcopy REPOS_PATH NEW_REPOS_PATH\n\n"
352 "Makes a hot copy of a repository.\n"),
353 {svnadmin__clean_logs} },
355 {"list-dblogs", subcommand_list_dblogs, {0}, N_
356 ("usage: svnadmin list-dblogs REPOS_PATH\n\n"
357 "List all Berkeley DB log files.\n\n"
358 "WARNING: Modifying or deleting logfiles which are still in use\n"
359 "will cause your repository to be corrupted.\n"),
360 {0} },
362 {"list-unused-dblogs", subcommand_list_unused_dblogs, {0}, N_
363 ("usage: svnadmin list-unused-dblogs REPOS_PATH\n\n"
364 "List unused Berkeley DB log files.\n\n"),
365 {0} },
367 {"load", subcommand_load, {0}, N_
368 ("usage: svnadmin load REPOS_PATH\n\n"
369 "Read a 'dumpfile'-formatted stream from stdin, committing\n"
370 "new revisions into the repository's filesystem. If the repository\n"
371 "was previously empty, its UUID will, by default, be changed to the\n"
372 "one specified in the stream. Progress feedback is sent to stdout.\n"),
373 {'q', svnadmin__ignore_uuid, svnadmin__force_uuid,
374 svnadmin__use_pre_commit_hook, svnadmin__use_post_commit_hook,
375 svnadmin__parent_dir} },
377 {"lslocks", subcommand_lslocks, {0}, N_
378 ("usage: svnadmin lslocks REPOS_PATH [PATH-IN-REPOS]\n\n"
379 "Print descriptions of all locks on or under PATH-IN-REPOS (which,\n"
380 "if not provided, is the root of the repository).\n"),
381 {0} },
383 {"lstxns", subcommand_lstxns, {0}, N_
384 ("usage: svnadmin lstxns REPOS_PATH\n\n"
385 "Print the names of all uncommitted transactions.\n"),
386 {0} },
388 {"recover", subcommand_recover, {0}, N_
389 ("usage: svnadmin recover REPOS_PATH\n\n"
390 "Run the recovery procedure on a repository. Do this if you've\n"
391 "been getting errors indicating that recovery ought to be run.\n"
392 "Berkeley DB recovery requires exclusive access and will\n"
393 "exit if the repository is in use by another process.\n"),
394 {svnadmin__wait} },
396 {"rmlocks", subcommand_rmlocks, {0}, N_
397 ("usage: svnadmin rmlocks REPOS_PATH LOCKED_PATH...\n\n"
398 "Unconditionally remove lock from each LOCKED_PATH.\n"),
399 {0} },
401 {"rmtxns", subcommand_rmtxns, {0}, N_
402 ("usage: svnadmin rmtxns REPOS_PATH TXN_NAME...\n\n"
403 "Delete the named transaction(s).\n"),
404 {'q'} },
406 {"setlog", subcommand_setlog, {0}, N_
407 ("usage: svnadmin setlog REPOS_PATH -r REVISION FILE\n\n"
408 "Set the log-message on revision REVISION to the contents of FILE. Use\n"
409 "--bypass-hooks to avoid triggering the revision-property-related hooks\n"
410 "(for example, if you do not want an email notification sent\n"
411 "from your post-revprop-change hook, or because the modification of\n"
412 "revision properties has not been enabled in the pre-revprop-change\n"
413 "hook).\n\n"
414 "NOTE: Revision properties are not versioned, so this command will\n"
415 "overwrite the previous log message.\n"),
416 {'r', svnadmin__bypass_hooks} },
418 {"setrevprop", subcommand_setrevprop, {0}, N_
419 ("usage: svnadmin setrevprop REPOS_PATH -r REVISION NAME FILE\n\n"
420 "Set the property NAME on revision REVISION to the contents of FILE. Use\n"
421 "--use-pre-revprop-change-hook/--use-post-revprop-change-hook to trigger\n"
422 "the revision property-related hooks (for example, if you want an email\n"
423 "notification sent from your post-revprop-change hook).\n\n"
424 "NOTE: Revision properties are not versioned, so this command will\n"
425 "overwrite the previous value of the property.\n"),
426 {'r', svnadmin__use_pre_revprop_change_hook,
427 svnadmin__use_post_revprop_change_hook} },
429 {"setuuid", subcommand_setuuid, {0}, N_
430 ("usage: svnadmin setuuid REPOS_PATH [NEW_UUID]\n\n"
431 "Reset the repository UUID for the repository located at REPOS_PATH. If\n"
432 "NEW_UUID is provided, use that as the new repository UUID; otherwise,\n"
433 "generate a brand new UUID for the repository.\n"),
434 {0} },
436 {"verify", subcommand_verify, {0}, N_
437 ("usage: svnadmin verify REPOS_PATH\n\n"
438 "Verifies the data stored in the repository.\n"),
439 {'r', 'q'} },
441 { NULL, NULL, {0}, NULL, {0} }
445 /* Baton for passing option/argument state to a subcommand function. */
446 struct svnadmin_opt_state
448 const char *repository_path;
449 const char *new_repository_path; /* hotcopy dest. path */
450 const char *fs_type; /* --fs-type */
451 svn_boolean_t pre_1_4_compatible; /* --pre-1.4-compatible */
452 svn_boolean_t pre_1_5_compatible; /* --pre-1.5-compatible */
453 svn_opt_revision_t start_revision, end_revision; /* -r X[:Y] */
454 svn_boolean_t help; /* --help or -? */
455 svn_boolean_t version; /* --version */
456 svn_boolean_t incremental; /* --incremental */
457 svn_boolean_t use_deltas; /* --deltas */
458 svn_boolean_t use_pre_commit_hook; /* --use-pre-commit-hook */
459 svn_boolean_t use_post_commit_hook; /* --use-post-commit-hook */
460 svn_boolean_t use_pre_revprop_change_hook; /* --use-pre-revprop-change-hook */
461 svn_boolean_t use_post_revprop_change_hook; /* --use-post-revprop-change-hook */
462 svn_boolean_t quiet; /* --quiet */
463 svn_boolean_t bdb_txn_nosync; /* --bdb-txn-nosync */
464 svn_boolean_t bdb_log_keep; /* --bdb-log-keep */
465 svn_boolean_t clean_logs; /* --clean-logs */
466 svn_boolean_t bypass_hooks; /* --bypass-hooks */
467 svn_boolean_t wait; /* --wait */
468 enum svn_repos_load_uuid uuid_action; /* --ignore-uuid,
469 --force-uuid */
470 const char *parent_dir;
472 const char *config_dir; /* Overriding Configuration Directory */
476 /* Set *REVNUM to the revision specified by REVISION (or to
477 SVN_INVALID_REVNUM if that has the type 'unspecified'),
478 possibly making use of the YOUNGEST revision number in REPOS. */
479 static svn_error_t *
480 get_revnum(svn_revnum_t *revnum, const svn_opt_revision_t *revision,
481 svn_revnum_t youngest, svn_repos_t *repos, apr_pool_t *pool)
483 if (revision->kind == svn_opt_revision_number)
484 *revnum = revision->value.number;
485 else if (revision->kind == svn_opt_revision_head)
486 *revnum = youngest;
487 else if (revision->kind == svn_opt_revision_date)
488 SVN_ERR(svn_repos_dated_revision
489 (revnum, repos, revision->value.date, pool));
490 else if (revision->kind == svn_opt_revision_unspecified)
491 *revnum = SVN_INVALID_REVNUM;
492 else
493 return svn_error_create
494 (SVN_ERR_CL_ARG_PARSING_ERROR, NULL, _("Invalid revision specifier"));
496 if (*revnum > youngest)
497 return svn_error_createf
498 (SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
499 _("Revisions must not be greater than the youngest revision (%ld)"),
500 youngest);
502 return SVN_NO_ERROR;
506 /* This implements `svn_opt_subcommand_t'. */
507 static svn_error_t *
508 subcommand_create(apr_getopt_t *os, void *baton, apr_pool_t *pool)
510 struct svnadmin_opt_state *opt_state = baton;
511 svn_repos_t *repos;
512 apr_hash_t *config;
513 apr_hash_t *fs_config = apr_hash_make(pool);
515 apr_hash_set(fs_config, SVN_FS_CONFIG_BDB_TXN_NOSYNC,
516 APR_HASH_KEY_STRING,
517 (opt_state->bdb_txn_nosync ? "1" : "0"));
519 apr_hash_set(fs_config, SVN_FS_CONFIG_BDB_LOG_AUTOREMOVE,
520 APR_HASH_KEY_STRING,
521 (opt_state->bdb_log_keep ? "0" : "1"));
523 if (opt_state->fs_type)
524 apr_hash_set(fs_config, SVN_FS_CONFIG_FS_TYPE,
525 APR_HASH_KEY_STRING,
526 opt_state->fs_type);
528 if (opt_state->pre_1_4_compatible)
529 apr_hash_set(fs_config, SVN_FS_CONFIG_PRE_1_4_COMPATIBLE,
530 APR_HASH_KEY_STRING,
531 "1");
533 if (opt_state->pre_1_5_compatible)
534 apr_hash_set(fs_config, SVN_FS_CONFIG_PRE_1_5_COMPATIBLE,
535 APR_HASH_KEY_STRING,
536 "1");
538 SVN_ERR(svn_config_get_config(&config, opt_state->config_dir, pool));
539 SVN_ERR(svn_repos_create(&repos, opt_state->repository_path,
540 NULL, NULL,
541 config, fs_config, pool));
542 svn_fs_set_warning_func(svn_repos_fs(repos), warning_func, NULL);
543 return SVN_NO_ERROR;
547 /* This implements `svn_opt_subcommand_t'. */
548 static svn_error_t *
549 subcommand_deltify(apr_getopt_t *os, void *baton, apr_pool_t *pool)
551 struct svnadmin_opt_state *opt_state = baton;
552 svn_repos_t *repos;
553 svn_fs_t *fs;
554 svn_revnum_t start = SVN_INVALID_REVNUM, end = SVN_INVALID_REVNUM;
555 svn_revnum_t youngest, revision;
556 apr_pool_t *subpool = svn_pool_create(pool);
558 SVN_ERR(open_repos(&repos, opt_state->repository_path, pool));
559 fs = svn_repos_fs(repos);
560 SVN_ERR(svn_fs_youngest_rev(&youngest, fs, pool));
562 /* Find the revision numbers at which to start and end. */
563 SVN_ERR(get_revnum(&start, &opt_state->start_revision,
564 youngest, repos, pool));
565 SVN_ERR(get_revnum(&end, &opt_state->end_revision,
566 youngest, repos, pool));
568 /* Fill in implied revisions if necessary. */
569 if (start == SVN_INVALID_REVNUM)
570 start = youngest;
571 if (end == SVN_INVALID_REVNUM)
572 end = start;
574 if (start > end)
575 return svn_error_create
576 (SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
577 _("First revision cannot be higher than second"));
579 /* Loop over the requested revision range, performing the
580 predecessor deltification on paths changed in each. */
581 for (revision = start; revision <= end; revision++)
583 svn_pool_clear(subpool);
584 SVN_ERR(check_cancel(NULL));
585 if (! opt_state->quiet)
586 SVN_ERR(svn_cmdline_printf(subpool, _("Deltifying revision %ld..."),
587 revision));
588 SVN_ERR(svn_fs_deltify_revision(fs, revision, subpool));
589 if (! opt_state->quiet)
590 SVN_ERR(svn_cmdline_printf(subpool, _("done.\n")));
592 svn_pool_destroy(subpool);
594 return SVN_NO_ERROR;
598 /* Baton for recode_write(). */
599 struct recode_write_baton
601 apr_pool_t *pool;
602 FILE *out;
605 /* This implements the 'svn_write_fn_t' interface.
607 Write DATA to ((struct recode_write_baton *) BATON)->out, in the
608 console encoding, using svn_cmdline_fprintf(). DATA is a
609 UTF8-encoded C string, therefore ignore LEN.
611 ### This recoding mechanism might want to be abstracted into
612 ### svn_io.h or svn_cmdline.h, if it proves useful elsewhere. */
613 static svn_error_t *recode_write(void *baton,
614 const char *data,
615 apr_size_t *len)
617 struct recode_write_baton *rwb = baton;
618 svn_pool_clear(rwb->pool);
619 return svn_cmdline_fputs(data, rwb->out, rwb->pool);
622 /* Create a stream, to write to STD_STREAM, that uses recode_write()
623 to perform UTF-8 to console encoding translation. */
624 static svn_stream_t *
625 recode_stream_create(FILE *std_stream, apr_pool_t *pool)
627 struct recode_write_baton *std_stream_rwb =
628 apr_palloc(pool, sizeof(struct recode_write_baton));
630 svn_stream_t *rw_stream = svn_stream_create(std_stream_rwb, pool);
631 std_stream_rwb->pool = svn_pool_create(pool);
632 std_stream_rwb->out = std_stream;
633 svn_stream_set_write(rw_stream, recode_write);
634 return rw_stream;
638 /* Common implementation for dump and verify. First three parameters mirror
639 the 'svn_opt_subcommand_t' type. The DUMP_CONTENTS parameter determines
640 whether to send the dump to stdout or an empty stream. */
641 static svn_error_t *
642 dump_repo(apr_getopt_t *os, void *baton,
643 apr_pool_t *pool, svn_boolean_t dump_contents,
644 svn_boolean_t incremental)
646 struct svnadmin_opt_state *opt_state = baton;
647 svn_repos_t *repos;
648 svn_fs_t *fs;
649 svn_stream_t *stdout_stream, *stderr_stream = NULL;
650 svn_revnum_t lower = SVN_INVALID_REVNUM, upper = SVN_INVALID_REVNUM;
651 svn_revnum_t youngest;
653 SVN_ERR(open_repos(&repos, opt_state->repository_path, pool));
654 fs = svn_repos_fs(repos);
655 SVN_ERR(svn_fs_youngest_rev(&youngest, fs, pool));
657 /* Find the revision numbers at which to start and end. */
658 SVN_ERR(get_revnum(&lower, &opt_state->start_revision,
659 youngest, repos, pool));
660 SVN_ERR(get_revnum(&upper, &opt_state->end_revision,
661 youngest, repos, pool));
663 /* Fill in implied revisions if necessary. */
664 if (lower == SVN_INVALID_REVNUM)
666 lower = 0;
667 upper = youngest;
669 else if (upper == SVN_INVALID_REVNUM)
671 upper = lower;
674 if (lower > upper)
675 return svn_error_create
676 (SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
677 _("First revision cannot be higher than second"));
679 /* Run the dump to STDOUT. Let the user redirect output into
680 a file if they want. :-) */
681 if (dump_contents)
682 SVN_ERR(create_stdio_stream(&stdout_stream, apr_file_open_stdout, pool));
683 else
684 stdout_stream = NULL;
686 /* Progress feedback goes to STDERR, unless they asked to suppress it. */
687 if (! opt_state->quiet)
688 stderr_stream = recode_stream_create(stderr, pool);
690 SVN_ERR(svn_repos_dump_fs2(repos, stdout_stream, stderr_stream,
691 lower, upper, incremental,
692 opt_state->use_deltas, check_cancel, NULL,
693 pool));
695 return SVN_NO_ERROR;
699 /* This implements `svn_opt_subcommand_t'. */
700 static svn_error_t *
701 subcommand_dump(apr_getopt_t *os, void *baton, apr_pool_t *pool)
703 struct svnadmin_opt_state *opt_state = baton;
704 return dump_repo(os, baton, pool, TRUE, opt_state->incremental);
708 /* This implements `svn_opt_subcommand_t'. */
709 static svn_error_t *
710 subcommand_help(apr_getopt_t *os, void *baton, apr_pool_t *pool)
712 struct svnadmin_opt_state *opt_state = baton;
713 const char *header =
714 _("general usage: svnadmin SUBCOMMAND REPOS_PATH [ARGS & OPTIONS ...]\n"
715 "Type 'svnadmin help <subcommand>' for help on a specific subcommand.\n"
716 "Type 'svnadmin --version' to see the program version and FS modules.\n"
717 "\n"
718 "Available subcommands:\n");
720 const char *fs_desc_start
721 = _("The following repository back-end (FS) modules are available:\n\n");
723 svn_stringbuf_t *version_footer;
725 version_footer = svn_stringbuf_create(fs_desc_start, pool);
726 SVN_ERR(svn_fs_print_modules(version_footer, pool));
728 SVN_ERR(svn_opt_print_help(os, "svnadmin",
729 opt_state ? opt_state->version : FALSE,
730 FALSE, version_footer->data,
731 header, cmd_table, options_table, NULL,
732 pool));
734 return SVN_NO_ERROR;
738 /* This implements `svn_opt_subcommand_t'. */
739 static svn_error_t *
740 subcommand_load(apr_getopt_t *os, void *baton, apr_pool_t *pool)
742 struct svnadmin_opt_state *opt_state = baton;
743 svn_repos_t *repos;
744 svn_stream_t *stdin_stream, *stdout_stream = NULL;
746 SVN_ERR(open_repos(&repos, opt_state->repository_path, pool));
748 /* Read the stream from STDIN. Users can redirect a file. */
749 SVN_ERR(create_stdio_stream(&stdin_stream,
750 apr_file_open_stdin, pool));
752 /* Progress feedback goes to STDOUT, unless they asked to suppress it. */
753 if (! opt_state->quiet)
754 stdout_stream = recode_stream_create(stdout, pool);
756 SVN_ERR(svn_repos_load_fs2(repos, stdin_stream, stdout_stream,
757 opt_state->uuid_action, opt_state->parent_dir,
758 opt_state->use_pre_commit_hook,
759 opt_state->use_post_commit_hook,
760 check_cancel, NULL, pool));
762 return SVN_NO_ERROR;
766 /* This implements `svn_opt_subcommand_t'. */
767 static svn_error_t *
768 subcommand_lstxns(apr_getopt_t *os, void *baton, apr_pool_t *pool)
770 struct svnadmin_opt_state *opt_state = baton;
771 svn_repos_t *repos;
772 svn_fs_t *fs;
773 apr_array_header_t *txns;
774 int i;
776 SVN_ERR(open_repos(&repos, opt_state->repository_path, pool));
777 fs = svn_repos_fs(repos);
778 SVN_ERR(svn_fs_list_transactions(&txns, fs, pool));
780 /* Loop, printing revisions. */
781 for (i = 0; i < txns->nelts; i++)
783 SVN_ERR(svn_cmdline_printf(pool, "%s\n",
784 APR_ARRAY_IDX(txns, i, const char *)));
787 return SVN_NO_ERROR;
791 /* A callback which is called when the recovery starts. */
792 static svn_error_t *
793 recovery_started(void *baton)
795 apr_pool_t *pool = (apr_pool_t *)baton;
797 SVN_ERR(svn_cmdline_printf(pool,
798 _("Repository lock acquired.\n"
799 "Please wait; recovering the"
800 " repository may take some time...\n")));
801 SVN_ERR(svn_cmdline_fflush(stdout));
803 /* Enable cancellation signal handlers. */
804 setup_cancellation_signals(signal_handler);
806 return SVN_NO_ERROR;
810 /* This implements `svn_opt_subcommand_t'. */
811 static svn_error_t *
812 subcommand_recover(apr_getopt_t *os, void *baton, apr_pool_t *pool)
814 svn_revnum_t youngest_rev;
815 svn_repos_t *repos;
816 svn_error_t *err;
817 struct svnadmin_opt_state *opt_state = baton;
819 /* Restore default signal handlers until after we have acquired the
820 * exclusive lock so that the user interrupt before we actually
821 * touch the repository. */
822 setup_cancellation_signals(SIG_DFL);
824 err = svn_repos_recover3(opt_state->repository_path, TRUE,
825 recovery_started, pool,
826 check_cancel, NULL, pool);
827 if (err)
829 if (! APR_STATUS_IS_EAGAIN(err->apr_err))
830 return err;
831 svn_error_clear(err);
832 if (! opt_state->wait)
833 return svn_error_create(SVN_ERR_REPOS_LOCKED, NULL,
834 _("Failed to get exclusive repository "
835 "access; perhaps another process\n"
836 "such as httpd, svnserve or svn "
837 "has it open?"));
838 SVN_ERR(svn_cmdline_printf(pool,
839 _("Waiting on repository lock; perhaps"
840 " another process has it open?\n")));
841 SVN_ERR(svn_cmdline_fflush(stdout));
842 SVN_ERR(svn_repos_recover3(opt_state->repository_path, FALSE,
843 recovery_started, pool,
844 check_cancel, NULL, pool));
847 SVN_ERR(svn_cmdline_printf(pool, _("\nRecovery completed.\n")));
849 /* Since db transactions may have been replayed, it's nice to tell
850 people what the latest revision is. It also proves that the
851 recovery actually worked. */
852 SVN_ERR(open_repos(&repos, opt_state->repository_path, pool));
853 SVN_ERR(svn_fs_youngest_rev(&youngest_rev, svn_repos_fs(repos), pool));
854 SVN_ERR(svn_cmdline_printf(pool, _("The latest repos revision is %ld.\n"),
855 youngest_rev));
857 return SVN_NO_ERROR;
861 /* This implements `svn_opt_subcommand_t'. */
862 static svn_error_t *
863 list_dblogs(apr_getopt_t *os, void *baton, svn_boolean_t only_unused,
864 apr_pool_t *pool)
866 struct svnadmin_opt_state *opt_state = baton;
867 apr_array_header_t *logfiles;
868 int i;
870 SVN_ERR(svn_repos_db_logfiles(&logfiles,
871 opt_state->repository_path,
872 only_unused,
873 pool));
875 /* Loop, printing log files. We append the log paths to the
876 repository path, making sure to return everything to the native
877 style before printing. */
878 for (i = 0; i < logfiles->nelts; i++)
880 const char *log_utf8;
881 log_utf8 = svn_path_join(opt_state->repository_path,
882 APR_ARRAY_IDX(logfiles, i, const char *),
883 pool);
884 log_utf8 = svn_path_local_style(log_utf8, pool);
885 SVN_ERR(svn_cmdline_printf(pool, "%s\n", log_utf8));
888 return SVN_NO_ERROR;
892 /* This implements `svn_opt_subcommand_t'. */
893 static svn_error_t *
894 subcommand_list_dblogs(apr_getopt_t *os, void *baton, apr_pool_t *pool)
896 SVN_ERR(list_dblogs(os, baton, FALSE, pool));
897 return SVN_NO_ERROR;
901 /* This implements `svn_opt_subcommand_t'. */
902 static svn_error_t *
903 subcommand_list_unused_dblogs(apr_getopt_t *os, void *baton, apr_pool_t *pool)
905 SVN_ERR(list_dblogs(os, baton, TRUE, pool));
906 return SVN_NO_ERROR;
910 /* This implements `svn_opt_subcommand_t'. */
911 static svn_error_t *
912 subcommand_rmtxns(apr_getopt_t *os, void *baton, apr_pool_t *pool)
914 struct svnadmin_opt_state *opt_state = baton;
915 svn_repos_t *repos;
916 svn_fs_t *fs;
917 svn_fs_txn_t *txn;
918 apr_array_header_t *args;
919 int i;
920 apr_pool_t *subpool = svn_pool_create(pool);
922 SVN_ERR(open_repos(&repos, opt_state->repository_path, pool));
923 fs = svn_repos_fs(repos);
925 SVN_ERR(svn_opt_parse_all_args(&args, os, pool));
927 /* All the rest of the arguments are transaction names. */
928 for (i = 0; i < args->nelts; i++)
930 const char *txn_name = APR_ARRAY_IDX(args, i, const char *);
931 const char *txn_name_utf8;
932 svn_error_t *err;
934 svn_pool_clear(subpool);
936 SVN_ERR(svn_utf_cstring_to_utf8(&txn_name_utf8, txn_name, subpool));
938 /* Try to open the txn. If that succeeds, try to abort it. */
939 err = svn_fs_open_txn(&txn, fs, txn_name_utf8, subpool);
940 if (! err)
941 err = svn_fs_abort_txn(txn, subpool);
943 /* If either the open or the abort of the txn fails because that
944 transaction is dead, just try to purge the thing. Else,
945 there was either an error worth reporting, or not error at
946 all. */
947 if (err && (err->apr_err == SVN_ERR_FS_TRANSACTION_DEAD))
949 svn_error_clear(err);
950 err = svn_fs_purge_txn(fs, txn_name_utf8, subpool);
953 /* If we had a real from the txn open, abort, or purge, we clear
954 that error and just report to the user that we had an issue
955 with this particular txn. */
956 if (err)
958 svn_handle_error2(err, stderr, FALSE /* non-fatal */, "svnadmin: ");
959 svn_error_clear(err);
961 else if (! opt_state->quiet)
963 SVN_ERR
964 (svn_cmdline_printf(subpool, _("Transaction '%s' removed.\n"),
965 txn_name));
969 svn_pool_destroy(subpool);
971 return SVN_NO_ERROR;
975 /* A helper for the 'setrevprop' and 'setlog' commands. Expects
976 OPT_STATE->use_pre_revprop_change_hook and
977 OPT_STATE->use_post_revprop_change_hook to be set appropriately. */
978 static svn_error_t *
979 set_revprop(const char *prop_name, const char *filename,
980 struct svnadmin_opt_state *opt_state, apr_pool_t *pool)
982 svn_repos_t *repos;
983 svn_string_t *prop_value = svn_string_create("", pool);
984 svn_stringbuf_t *file_contents;
985 const char *filename_utf8;
987 SVN_ERR(svn_utf_cstring_to_utf8(&filename_utf8, filename, pool));
988 filename_utf8 = svn_path_internal_style(filename_utf8, pool);
990 SVN_ERR(svn_stringbuf_from_file(&file_contents, filename_utf8, pool));
992 prop_value->data = file_contents->data;
993 prop_value->len = file_contents->len;
995 SVN_ERR(svn_subst_translate_string(&prop_value, prop_value, NULL, pool));
997 /* Open the filesystem */
998 SVN_ERR(open_repos(&repos, opt_state->repository_path, pool));
1000 /* If we are bypassing the hooks system, we just hit the filesystem
1001 directly. */
1002 if (opt_state->use_pre_revprop_change_hook ||
1003 opt_state->use_post_revprop_change_hook)
1005 SVN_ERR(svn_repos_fs_change_rev_prop3
1006 (repos, opt_state->start_revision.value.number,
1007 NULL, prop_name, prop_value,
1008 opt_state->use_pre_revprop_change_hook,
1009 opt_state->use_post_revprop_change_hook, NULL, NULL, pool));
1011 else
1013 svn_fs_t *fs = svn_repos_fs(repos);
1014 SVN_ERR(svn_fs_change_rev_prop
1015 (fs, opt_state->start_revision.value.number,
1016 prop_name, prop_value, pool));
1019 return SVN_NO_ERROR;
1023 /* This implements `svn_opt_subcommand_t'. */
1024 static svn_error_t *
1025 subcommand_setrevprop(apr_getopt_t *os, void *baton, apr_pool_t *pool)
1027 struct svnadmin_opt_state *opt_state = baton;
1028 apr_array_header_t *args;
1030 if (opt_state->start_revision.kind != svn_opt_revision_number)
1031 return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
1032 _("Missing revision"));
1033 else if (opt_state->end_revision.kind != svn_opt_revision_unspecified)
1034 return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
1035 _("Only one revision allowed"));
1037 SVN_ERR(svn_opt_parse_all_args(&args, os, pool));
1039 if (args->nelts != 2)
1040 return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
1041 _("Exactly one property name and one file "
1042 "argument required"));
1044 return set_revprop(APR_ARRAY_IDX(args, 0, const char *),
1045 APR_ARRAY_IDX(args, 1, const char *),
1046 opt_state, pool);
1050 /* This implements `svn_opt_subcommand_t'. */
1051 static svn_error_t *
1052 subcommand_setuuid(apr_getopt_t *os, void *baton, apr_pool_t *pool)
1054 struct svnadmin_opt_state *opt_state = baton;
1055 apr_array_header_t *args;
1056 svn_repos_t *repos;
1057 svn_fs_t *fs;
1058 const char *uuid = NULL;
1060 SVN_ERR(svn_opt_parse_all_args(&args, os, pool));
1062 if (args->nelts > 1)
1063 return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL, NULL);
1064 if (args->nelts == 1)
1065 uuid = APR_ARRAY_IDX(args, 0, const char *);
1067 SVN_ERR(open_repos(&repos, opt_state->repository_path, pool));
1068 fs = svn_repos_fs(repos);
1069 return svn_fs_set_uuid(fs, uuid, pool);
1073 /* This implements `svn_opt_subcommand_t'. */
1074 static svn_error_t *
1075 subcommand_setlog(apr_getopt_t *os, void *baton, apr_pool_t *pool)
1077 struct svnadmin_opt_state *opt_state = baton;
1078 apr_array_header_t *args;
1080 if (opt_state->start_revision.kind != svn_opt_revision_number)
1081 return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
1082 _("Missing revision"));
1083 else if (opt_state->end_revision.kind != svn_opt_revision_unspecified)
1084 return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
1085 _("Only one revision allowed"));
1087 SVN_ERR(svn_opt_parse_all_args(&args, os, pool));
1089 if (args->nelts != 1)
1090 return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
1091 _("Exactly one file argument required"));
1093 /* set_revprop() responds only to pre-/post-revprop-change opts. */
1094 if (!opt_state->bypass_hooks)
1096 opt_state->use_pre_revprop_change_hook = TRUE;
1097 opt_state->use_post_revprop_change_hook = TRUE;
1100 return set_revprop(SVN_PROP_REVISION_LOG,
1101 APR_ARRAY_IDX(args, 0, const char *),
1102 opt_state, pool);
1106 /* This implements `svn_opt_subcommand_t'. */
1107 static svn_error_t *
1108 subcommand_verify(apr_getopt_t *os, void *baton, apr_pool_t *pool)
1110 struct svnadmin_opt_state *opt_state = baton;
1111 return dump_repo(os, baton, pool, FALSE,
1112 opt_state->start_revision.kind != svn_opt_revision_unspecified);
1116 /* This implements `svn_opt_subcommand_t'. */
1117 svn_error_t *
1118 subcommand_hotcopy(apr_getopt_t *os, void *baton, apr_pool_t *pool)
1120 struct svnadmin_opt_state *opt_state = baton;
1122 SVN_ERR(svn_repos_hotcopy(opt_state->repository_path,
1123 opt_state->new_repository_path,
1124 opt_state->clean_logs,
1125 pool));
1127 return SVN_NO_ERROR;
1131 static svn_error_t *
1132 subcommand_lslocks(apr_getopt_t *os, void *baton, apr_pool_t *pool)
1134 struct svnadmin_opt_state *opt_state = baton;
1135 apr_array_header_t *targets;
1136 svn_repos_t *repos;
1137 const char *fs_path = "/";
1138 svn_fs_t *fs;
1139 apr_hash_t *locks;
1140 apr_hash_index_t *hi;
1142 SVN_ERR(svn_opt_args_to_target_array2(&targets, os,
1143 apr_array_make(pool, 0,
1144 sizeof(const char *)),
1145 pool));
1146 if (targets->nelts > 1)
1147 return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, 0, NULL);
1148 if (targets->nelts)
1149 fs_path = APR_ARRAY_IDX(targets, 0, const char *);
1151 SVN_ERR(open_repos(&repos, opt_state->repository_path, pool));
1152 fs = svn_repos_fs(repos);
1154 /* Fetch all locks on or below the root directory. */
1155 SVN_ERR(svn_repos_fs_get_locks(&locks, repos, fs_path, NULL, NULL, pool));
1157 for (hi = apr_hash_first(pool, locks); hi; hi = apr_hash_next(hi))
1159 const void *key;
1160 void *val;
1161 const char *path, *cr_date, *exp_date = "";
1162 svn_lock_t *lock;
1163 int comment_lines = 0;
1165 apr_hash_this(hi, &key, NULL, &val);
1166 path = key;
1167 lock = val;
1169 cr_date = svn_time_to_human_cstring(lock->creation_date, pool);
1171 if (lock->expiration_date)
1172 exp_date = svn_time_to_human_cstring(lock->expiration_date, pool);
1174 if (lock->comment)
1175 comment_lines = svn_cstring_count_newlines(lock->comment) + 1;
1177 SVN_ERR(svn_cmdline_printf(pool, _("Path: %s\n"), path));
1178 SVN_ERR(svn_cmdline_printf(pool, _("UUID Token: %s\n"), lock->token));
1179 SVN_ERR(svn_cmdline_printf(pool, _("Owner: %s\n"), lock->owner));
1180 SVN_ERR(svn_cmdline_printf(pool, _("Created: %s\n"), cr_date));
1181 SVN_ERR(svn_cmdline_printf(pool, _("Expires: %s\n"), exp_date));
1182 SVN_ERR(svn_cmdline_printf(pool, (comment_lines != 1)
1183 ? _("Comment (%i lines):\n%s\n\n")
1184 : _("Comment (%i line):\n%s\n\n"),
1185 comment_lines,
1186 lock->comment ? lock->comment : ""));
1189 return SVN_NO_ERROR;
1194 static svn_error_t *
1195 subcommand_rmlocks(apr_getopt_t *os, void *baton, apr_pool_t *pool)
1197 struct svnadmin_opt_state *opt_state = baton;
1198 svn_repos_t *repos;
1199 svn_fs_t *fs;
1200 svn_fs_access_t *access;
1201 svn_error_t *err;
1202 apr_array_header_t *args;
1203 int i;
1204 const char *username;
1205 apr_pool_t *subpool = svn_pool_create(pool);
1207 SVN_ERR(open_repos(&repos, opt_state->repository_path, pool));
1208 fs = svn_repos_fs(repos);
1210 /* svn_fs_unlock() demands that some username be associated with the
1211 filesystem, so just use the UID of the person running 'svnadmin'.*/
1212 username = svn_user_get_name(pool);
1213 if (! username)
1214 username = "administrator";
1216 /* Create an access context describing the current user. */
1217 SVN_ERR(svn_fs_create_access(&access, username, pool));
1219 /* Attach the access context to the filesystem. */
1220 SVN_ERR(svn_fs_set_access(fs, access));
1222 /* Parse out any options. */
1223 SVN_ERR(svn_opt_parse_all_args(&args, os, pool));
1225 /* Our usage requires at least one FS path. */
1226 if (args->nelts == 0)
1227 return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, 0,
1228 _("No paths to unlock provided"));
1230 /* All the rest of the arguments are paths from which to remove locks. */
1231 for (i = 0; i < args->nelts; i++)
1233 const char *lock_path = APR_ARRAY_IDX(args, i, const char *);
1234 const char *lock_path_utf8;
1235 svn_lock_t *lock;
1237 SVN_ERR(svn_utf_cstring_to_utf8(&lock_path_utf8, lock_path, subpool));
1239 /* Fetch the path's svn_lock_t. */
1240 err = svn_fs_get_lock(&lock, fs, lock_path_utf8, subpool);
1241 if (err)
1242 goto move_on;
1243 if (! lock)
1245 SVN_ERR(svn_cmdline_printf(subpool,
1246 _("Path '%s' isn't locked.\n"),
1247 lock_path));
1248 continue;
1251 /* Now forcibly destroy the lock. */
1252 err = svn_fs_unlock(fs, lock_path_utf8,
1253 lock->token, 1 /* force */, subpool);
1254 if (err)
1255 goto move_on;
1257 SVN_ERR(svn_cmdline_printf(subpool,
1258 _("Removed lock on '%s'.\n"), lock->path));
1260 move_on:
1261 if (err)
1263 /* Print the error, but move on to the next lock. */
1264 svn_handle_error2(err, stderr, FALSE /* non-fatal */, "svnadmin: ");
1265 svn_error_clear(err);
1268 svn_pool_clear(subpool);
1271 svn_pool_destroy(subpool);
1272 return SVN_NO_ERROR;
1278 /** Main. **/
1281 main(int argc, const char *argv[])
1283 svn_error_t *err;
1284 apr_status_t apr_err;
1285 apr_allocator_t *allocator;
1286 apr_pool_t *pool;
1288 const svn_opt_subcommand_desc_t *subcommand = NULL;
1289 struct svnadmin_opt_state opt_state;
1290 apr_getopt_t *os;
1291 int opt_id;
1292 apr_array_header_t *received_opts;
1293 int i;
1295 /* Initialize the app. */
1296 if (svn_cmdline_init("svnadmin", stderr) != EXIT_SUCCESS)
1297 return EXIT_FAILURE;
1299 /* Create our top-level pool. Use a seperate mutexless allocator,
1300 * given this application is single threaded.
1302 if (apr_allocator_create(&allocator))
1303 return EXIT_FAILURE;
1305 apr_allocator_max_free_set(allocator, SVN_ALLOCATOR_RECOMMENDED_MAX_FREE);
1307 pool = svn_pool_create_ex(NULL, allocator);
1308 apr_allocator_owner_set(allocator, pool);
1310 received_opts = apr_array_make(pool, SVN_OPT_MAX_OPTIONS, sizeof(int));
1312 /* Check library versions */
1313 err = check_lib_versions();
1314 if (err)
1315 return svn_cmdline_handle_exit_error(err, pool, "svnadmin: ");
1317 /* Initialize the FS library. */
1318 err = svn_fs_initialize(pool);
1319 if (err)
1320 return svn_cmdline_handle_exit_error(err, pool, "svnadmin: ");
1322 if (argc <= 1)
1324 subcommand_help(NULL, NULL, pool);
1325 svn_pool_destroy(pool);
1326 return EXIT_FAILURE;
1329 /* Initialize opt_state. */
1330 memset(&opt_state, 0, sizeof(opt_state));
1331 opt_state.start_revision.kind = svn_opt_revision_unspecified;
1332 opt_state.end_revision.kind = svn_opt_revision_unspecified;
1334 /* Parse options. */
1335 err = svn_cmdline__getopt_init(&os, argc, argv, pool);
1336 if (err)
1337 return svn_cmdline_handle_exit_error(err, pool, "svnadmin: ");
1339 os->interleave = 1;
1341 while (1)
1343 const char *opt_arg;
1344 const char *utf8_opt_arg;
1346 /* Parse the next option. */
1347 apr_err = apr_getopt_long(os, options_table, &opt_id, &opt_arg);
1348 if (APR_STATUS_IS_EOF(apr_err))
1349 break;
1350 else if (apr_err)
1352 subcommand_help(NULL, NULL, pool);
1353 svn_pool_destroy(pool);
1354 return EXIT_FAILURE;
1357 /* Stash the option code in an array before parsing it. */
1358 APR_ARRAY_PUSH(received_opts, int) = opt_id;
1360 switch (opt_id) {
1361 case 'r':
1363 if (opt_state.start_revision.kind != svn_opt_revision_unspecified)
1365 err = svn_error_create
1366 (SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
1367 _("Multiple revision arguments encountered; "
1368 "try '-r N:M' instead of '-r N -r M'"));
1369 return svn_cmdline_handle_exit_error(err, pool, "svnadmin: ");
1371 if (svn_opt_parse_revision(&(opt_state.start_revision),
1372 &(opt_state.end_revision),
1373 opt_arg, pool) != 0)
1375 err = svn_utf_cstring_to_utf8(&utf8_opt_arg, opt_arg,
1376 pool);
1378 if (! err)
1379 err = svn_error_createf
1380 (SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
1381 _("Syntax error in revision argument '%s'"),
1382 utf8_opt_arg);
1383 return svn_cmdline_handle_exit_error(err, pool, "svnadmin: ");
1386 break;
1387 case 'q':
1388 opt_state.quiet = TRUE;
1389 break;
1390 case 'h':
1391 case '?':
1392 opt_state.help = TRUE;
1393 break;
1394 case svnadmin__version:
1395 opt_state.version = TRUE;
1396 break;
1397 case svnadmin__incremental:
1398 opt_state.incremental = TRUE;
1399 break;
1400 case svnadmin__deltas:
1401 opt_state.use_deltas = TRUE;
1402 break;
1403 case svnadmin__ignore_uuid:
1404 opt_state.uuid_action = svn_repos_load_uuid_ignore;
1405 break;
1406 case svnadmin__force_uuid:
1407 opt_state.uuid_action = svn_repos_load_uuid_force;
1408 break;
1409 case svnadmin__pre_1_4_compatible:
1410 opt_state.pre_1_4_compatible = TRUE;
1411 break;
1412 case svnadmin__pre_1_5_compatible:
1413 opt_state.pre_1_5_compatible = TRUE;
1414 break;
1415 case svnadmin__fs_type:
1416 err = svn_utf_cstring_to_utf8(&opt_state.fs_type, opt_arg, pool);
1417 if (err)
1418 return svn_cmdline_handle_exit_error(err, pool, "svnadmin: ");
1419 break;
1420 case svnadmin__parent_dir:
1421 err = svn_utf_cstring_to_utf8(&opt_state.parent_dir, opt_arg,
1422 pool);
1423 if (err)
1424 return svn_cmdline_handle_exit_error(err, pool, "svnadmin: ");
1425 opt_state.parent_dir
1426 = svn_path_internal_style(opt_state.parent_dir, pool);
1427 break;
1428 case svnadmin__use_pre_commit_hook:
1429 opt_state.use_pre_commit_hook = TRUE;
1430 break;
1431 case svnadmin__use_post_commit_hook:
1432 opt_state.use_post_commit_hook = TRUE;
1433 break;
1434 case svnadmin__use_pre_revprop_change_hook:
1435 opt_state.use_pre_revprop_change_hook = TRUE;
1436 break;
1437 case svnadmin__use_post_revprop_change_hook:
1438 opt_state.use_post_revprop_change_hook = TRUE;
1439 break;
1440 case svnadmin__bdb_txn_nosync:
1441 opt_state.bdb_txn_nosync = TRUE;
1442 break;
1443 case svnadmin__bdb_log_keep:
1444 opt_state.bdb_log_keep = TRUE;
1445 break;
1446 case svnadmin__bypass_hooks:
1447 opt_state.bypass_hooks = TRUE;
1448 break;
1449 case svnadmin__clean_logs:
1450 opt_state.clean_logs = TRUE;
1451 break;
1452 case svnadmin__config_dir:
1453 opt_state.config_dir =
1454 apr_pstrdup(pool, svn_path_canonicalize(opt_arg, pool));
1455 break;
1456 case svnadmin__wait:
1457 opt_state.wait = TRUE;
1458 break;
1459 default:
1461 subcommand_help(NULL, NULL, pool);
1462 svn_pool_destroy(pool);
1463 return EXIT_FAILURE;
1465 } /* close `switch' */
1466 } /* close `while' */
1468 /* If the user asked for help, then the rest of the arguments are
1469 the names of subcommands to get help on (if any), or else they're
1470 just typos/mistakes. Whatever the case, the subcommand to
1471 actually run is subcommand_help(). */
1472 if (opt_state.help)
1473 subcommand = svn_opt_get_canonical_subcommand(cmd_table, "help");
1475 /* If we're not running the `help' subcommand, then look for a
1476 subcommand in the first argument. */
1477 if (subcommand == NULL)
1479 if (os->ind >= os->argc)
1481 if (opt_state.version)
1483 /* Use the "help" subcommand to handle the "--version" option. */
1484 static const svn_opt_subcommand_desc_t pseudo_cmd =
1485 { "--version", subcommand_help, {0}, "",
1486 {svnadmin__version, /* must accept its own option */
1487 } };
1489 subcommand = &pseudo_cmd;
1491 else
1493 svn_error_clear
1494 (svn_cmdline_fprintf(stderr, pool,
1495 _("subcommand argument required\n")));
1496 subcommand_help(NULL, NULL, pool);
1497 svn_pool_destroy(pool);
1498 return EXIT_FAILURE;
1501 else
1503 const char *first_arg = os->argv[os->ind++];
1504 subcommand = svn_opt_get_canonical_subcommand(cmd_table, first_arg);
1505 if (subcommand == NULL)
1507 const char* first_arg_utf8;
1508 err = svn_utf_cstring_to_utf8(&first_arg_utf8, first_arg, pool);
1509 if (err)
1510 return svn_cmdline_handle_exit_error(err, pool, "svnadmin: ");
1511 svn_error_clear
1512 (svn_cmdline_fprintf(stderr, pool,
1513 _("Unknown command: '%s'\n"),
1514 first_arg_utf8));
1515 subcommand_help(NULL, NULL, pool);
1516 svn_pool_destroy(pool);
1517 return EXIT_FAILURE;
1522 /* If there's a second argument, it's probably the repository.
1523 Every subcommand except `help' requires one, so we parse it out
1524 here and store it in opt_state. */
1525 if (subcommand->cmd_func != subcommand_help)
1527 err = parse_local_repos_path(os,
1528 &(opt_state.repository_path),
1529 pool);
1530 if (err)
1531 return svn_cmdline_handle_exit_error(err, pool, "svnadmin: ");
1535 /* If command is hot copy the third argument will be the new
1536 repository path. */
1537 if (subcommand->cmd_func == subcommand_hotcopy)
1539 err = parse_local_repos_path(os,
1540 &(opt_state.new_repository_path),
1541 pool);
1542 if (err)
1543 return svn_cmdline_handle_exit_error(err, pool, "svnadmin: ");
1546 /* Check that the subcommand wasn't passed any inappropriate options. */
1547 for (i = 0; i < received_opts->nelts; i++)
1549 opt_id = APR_ARRAY_IDX(received_opts, i, int);
1551 /* All commands implicitly accept --help, so just skip over this
1552 when we see it. Note that we don't want to include this option
1553 in their "accepted options" list because it would be awfully
1554 redundant to display it in every commands' help text. */
1555 if (opt_id == 'h' || opt_id == '?')
1556 continue;
1558 if (! svn_opt_subcommand_takes_option(subcommand, opt_id))
1560 const char *optstr;
1561 const apr_getopt_option_t *badopt =
1562 svn_opt_get_option_from_code(opt_id, options_table);
1563 svn_opt_format_option(&optstr, badopt, FALSE, pool);
1564 if (subcommand->name[0] == '-')
1565 subcommand_help(NULL, NULL, pool);
1566 else
1567 svn_error_clear
1568 (svn_cmdline_fprintf
1569 (stderr, pool, _("Subcommand '%s' doesn't accept option '%s'\n"
1570 "Type 'svnadmin help %s' for usage.\n"),
1571 subcommand->name, optstr, subcommand->name));
1572 svn_pool_destroy(pool);
1573 return EXIT_FAILURE;
1577 /* Set up our cancellation support. */
1578 setup_cancellation_signals(signal_handler);
1580 #ifdef SIGPIPE
1581 /* Disable SIGPIPE generation for the platforms that have it. */
1582 apr_signal(SIGPIPE, SIG_IGN);
1583 #endif
1585 #ifdef SIGXFSZ
1586 /* Disable SIGXFSZ generation for the platforms that have it, otherwise
1587 * working with large files when compiled against an APR that doesn't have
1588 * large file support will crash the program, which is uncool. */
1589 apr_signal(SIGXFSZ, SIG_IGN);
1590 #endif
1592 /* Run the subcommand. */
1593 err = (*subcommand->cmd_func)(os, &opt_state, pool);
1594 if (err)
1596 /* For argument-related problems, suggest using the 'help'
1597 subcommand. */
1598 if (err->apr_err == SVN_ERR_CL_INSUFFICIENT_ARGS
1599 || err->apr_err == SVN_ERR_CL_ARG_PARSING_ERROR)
1601 err = svn_error_quick_wrap(err,
1602 _("Try 'svnadmin help' for more info"));
1604 return svn_cmdline_handle_exit_error(err, pool, "svnadmin: ");
1606 else
1608 svn_pool_destroy(pool);
1609 /* Ensure that everything is written to stdout, so the user will
1610 see any print errors. */
1611 err = svn_cmdline_fflush(stdout);
1612 if (err) {
1613 svn_handle_error2(err, stderr, FALSE, "svnadmin: ");
1614 svn_error_clear(err);
1615 return EXIT_FAILURE;
1617 return EXIT_SUCCESS;
1622 /* This implements `svn_opt_subcommand_t'. */
1623 static svn_error_t *
1624 subcommand_crashtest(apr_getopt_t *os, void *baton, apr_pool_t *pool)
1626 struct svnadmin_opt_state *opt_state = baton;
1627 svn_repos_t *repos;
1629 SVN_ERR(open_repos(&repos, opt_state->repository_path, pool));
1630 abort();
1632 return SVN_NO_ERROR;