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"
28 #include "svn_subst.h"
30 #include "svn_config.h"
31 #include "svn_repos.h"
33 #include "svn_version.h"
34 #include "svn_props.h"
38 #include "svn_private_config.h"
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. */
48 signal_handler(int signum
)
50 apr_signal(signum
, SIG_IGN
);
55 /* A helper to set up the cancellation signal handlers. */
57 setup_cancellation_signals(void (*handler
)(int signum
))
59 apr_signal(SIGINT
, handler
);
61 /* SIGBREAK is a Win32 specific signal generated by ctrl-break. */
62 apr_signal(SIGBREAK
, handler
);
65 apr_signal(SIGHUP
, handler
);
68 apr_signal(SIGTERM
, handler
);
73 /* Our cancellation callback. */
75 check_cancel(void *baton
)
78 return svn_error_create(SVN_ERR_CANCELLED
, NULL
, _("Caught signal"));
84 /* Helper to open stdio streams */
86 create_stdio_stream(svn_stream_t
**stream
,
87 APR_DECLARE(apr_status_t
) open_fn(apr_file_t
**,
91 apr_file_t
*stdio_file
;
92 apr_status_t apr_err
= open_fn(&stdio_file
, pool
);
95 return svn_error_wrap_apr(apr_err
, _("Can't open stdio file"));
97 *stream
= svn_stream_from_aprfile(stdio_file
, pool
);
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.
107 parse_local_repos_path(apr_getopt_t
*os
,
108 const char ** repos_path
,
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"),
137 /* Custom filesystem warning function. */
139 warning_func(void *baton
,
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). */
151 open_repos(svn_repos_t
**repos
,
155 SVN_ERR(svn_repos_open(repos
, path
, pool
));
156 svn_fs_set_warning_func(svn_repos_fs(*repos
), warning_func
, NULL
);
161 /* Version compatibility check */
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
},
174 SVN_VERSION_DEFINE(my_version
);
175 return svn_ver_check_list(&my_version
, checklist
);
182 static svn_opt_subcommand_t
183 subcommand_crashtest
,
190 subcommand_list_dblogs
,
191 subcommand_list_unused_dblogs
,
198 subcommand_setrevprop
,
205 svnadmin__version
= SVN_OPT_FIRST_LONGOPT_ID
,
206 svnadmin__incremental
,
208 svnadmin__ignore_uuid
,
209 svnadmin__force_uuid
,
211 svnadmin__parent_dir
,
212 svnadmin__bdb_txn_nosync
,
213 svnadmin__bdb_log_keep
,
214 svnadmin__config_dir
,
215 svnadmin__bypass_hooks
,
216 svnadmin__use_pre_commit_hook
,
217 svnadmin__use_post_commit_hook
,
218 svnadmin__use_pre_revprop_change_hook
,
219 svnadmin__use_post_revprop_change_hook
,
220 svnadmin__clean_logs
,
222 svnadmin__pre_1_4_compatible
,
223 svnadmin__pre_1_5_compatible
226 /* Option codes and descriptions.
228 * The entire list must be terminated with an entry of nulls.
230 static const apr_getopt_option_t options_table
[] =
233 N_("show help on a subcommand")},
236 N_("show help on a subcommand")},
238 {"version", svnadmin__version
, 0,
239 N_("show program version information")},
242 N_("specify revision number ARG (or X:Y range)")},
244 {"incremental", svnadmin__incremental
, 0,
245 N_("dump incrementally")},
247 {"deltas", svnadmin__deltas
, 0,
248 N_("use deltas in dump output")},
250 {"bypass-hooks", svnadmin__bypass_hooks
, 0,
251 N_("bypass the repository hook system")},
254 N_("no progress (only errors) to stderr")},
256 {"ignore-uuid", svnadmin__ignore_uuid
, 0,
257 N_("ignore any repos UUID found in the stream")},
259 {"force-uuid", svnadmin__force_uuid
, 0,
260 N_("set repos UUID to that found in stream, if any")},
262 {"fs-type", svnadmin__fs_type
, 1,
263 N_("type of repository: 'fsfs' (default) or 'bdb'")},
265 {"parent-dir", svnadmin__parent_dir
, 1,
266 N_("load at specified directory in repository")},
268 {"bdb-txn-nosync", svnadmin__bdb_txn_nosync
, 0,
269 N_("disable fsync at transaction commit [Berkeley DB]")},
271 {"bdb-log-keep", svnadmin__bdb_log_keep
, 0,
272 N_("disable automatic log file removal [Berkeley DB]")},
274 {"config-dir", svnadmin__config_dir
, 1,
275 N_("read user configuration files from directory ARG")},
277 {"clean-logs", svnadmin__clean_logs
, 0,
278 N_("remove redundant Berkeley DB log files\n"
279 " from source repository [Berkeley DB]")},
281 {"use-pre-commit-hook", svnadmin__use_pre_commit_hook
, 0,
282 N_("call pre-commit hook before committing revisions")},
284 {"use-post-commit-hook", svnadmin__use_post_commit_hook
, 0,
285 N_("call post-commit hook after committing revisions")},
287 {"use-pre-revprop-change-hook", svnadmin__use_pre_revprop_change_hook
, 0,
288 N_("call hook before changing revision property")},
290 {"use-post-revprop-change-hook", svnadmin__use_post_revprop_change_hook
, 0,
291 N_("call hook after changing revision property")},
293 {"wait", svnadmin__wait
, 0,
294 N_("wait instead of exit if the repository is in\n"
295 " use by another process")},
297 {"pre-1.4-compatible", svnadmin__pre_1_4_compatible
, 0,
298 N_("use format compatible with Subversion versions\n"
299 " earlier than 1.4")},
301 {"pre-1.5-compatible", svnadmin__pre_1_5_compatible
, 0,
302 N_("use format compatible with Subversion versions\n"
303 " earlier than 1.5")},
309 /* Array of available subcommands.
310 * The entire list must be terminated with an entry of nulls.
312 static const svn_opt_subcommand_desc_t cmd_table
[] =
314 {"crashtest", subcommand_crashtest
, {0}, N_
315 ("usage: svnadmin crashtest REPOS_PATH\n\n"
316 "Open the repository at REPOS_PATH, then abort, thus simulating\n"
317 "a process that crashes while holding an open repository handle.\n"),
320 {"create", subcommand_create
, {0}, N_
321 ("usage: svnadmin create REPOS_PATH\n\n"
322 "Create a new, empty repository at REPOS_PATH.\n"),
323 {svnadmin__bdb_txn_nosync
, svnadmin__bdb_log_keep
,
324 svnadmin__config_dir
, svnadmin__fs_type
, svnadmin__pre_1_4_compatible
,
325 svnadmin__pre_1_5_compatible
} },
327 {"deltify", subcommand_deltify
, {0}, N_
328 ("usage: svnadmin deltify [-r LOWER[:UPPER]] REPOS_PATH\n\n"
329 "Run over the requested revision range, performing predecessor delti-\n"
330 "fication on the paths changed in those revisions. Deltification in\n"
331 "essence compresses the repository by only storing the differences or\n"
332 "delta from the preceding revision. If no revisions are specified,\n"
333 "this will simply deltify the HEAD revision.\n"),
336 {"dump", subcommand_dump
, {0}, N_
337 ("usage: svnadmin dump REPOS_PATH [-r LOWER[:UPPER]] [--incremental]\n\n"
338 "Dump the contents of filesystem to stdout in a 'dumpfile'\n"
339 "portable format, sending feedback to stderr. Dump revisions\n"
340 "LOWER rev through UPPER rev. If no revisions are given, dump all\n"
341 "revision trees. If only LOWER is given, dump that one revision tree.\n"
342 "If --incremental is passed, then the first revision dumped will be\n"
343 "a diff against the previous revision, instead of the usual fulltext.\n"),
344 {'r', svnadmin__incremental
, svnadmin__deltas
, 'q'} },
346 {"help", subcommand_help
, {"?", "h"}, N_
347 ("usage: svnadmin help [SUBCOMMAND...]\n\n"
348 "Describe the usage of this program or its subcommands.\n"),
351 {"hotcopy", subcommand_hotcopy
, {0}, N_
352 ("usage: svnadmin hotcopy REPOS_PATH NEW_REPOS_PATH\n\n"
353 "Makes a hot copy of a repository.\n"),
354 {svnadmin__clean_logs
} },
356 {"list-dblogs", subcommand_list_dblogs
, {0}, N_
357 ("usage: svnadmin list-dblogs REPOS_PATH\n\n"
358 "List all Berkeley DB log files.\n\n"
359 "WARNING: Modifying or deleting logfiles which are still in use\n"
360 "will cause your repository to be corrupted.\n"),
363 {"list-unused-dblogs", subcommand_list_unused_dblogs
, {0}, N_
364 ("usage: svnadmin list-unused-dblogs REPOS_PATH\n\n"
365 "List unused Berkeley DB log files.\n\n"),
368 {"load", subcommand_load
, {0}, N_
369 ("usage: svnadmin load REPOS_PATH\n\n"
370 "Read a 'dumpfile'-formatted stream from stdin, committing\n"
371 "new revisions into the repository's filesystem. If the repository\n"
372 "was previously empty, its UUID will, by default, be changed to the\n"
373 "one specified in the stream. Progress feedback is sent to stdout.\n"),
374 {'q', svnadmin__ignore_uuid
, svnadmin__force_uuid
,
375 svnadmin__use_pre_commit_hook
, svnadmin__use_post_commit_hook
,
376 svnadmin__parent_dir
} },
378 {"lslocks", subcommand_lslocks
, {0}, N_
379 ("usage: svnadmin lslocks REPOS_PATH [PATH-IN-REPOS]\n\n"
380 "Print descriptions of all locks on or under PATH-IN-REPOS (which,\n"
381 "if not provided, is the root of the repository).\n"),
384 {"lstxns", subcommand_lstxns
, {0}, N_
385 ("usage: svnadmin lstxns REPOS_PATH\n\n"
386 "Print the names of all uncommitted transactions.\n"),
389 {"recover", subcommand_recover
, {0}, N_
390 ("usage: svnadmin recover REPOS_PATH\n\n"
391 "Run the recovery procedure on a repository. Do this if you've\n"
392 "been getting errors indicating that recovery ought to be run.\n"
393 "Berkeley DB recovery requires exclusive access and will\n"
394 "exit if the repository is in use by another process.\n"),
397 {"rmlocks", subcommand_rmlocks
, {0}, N_
398 ("usage: svnadmin rmlocks REPOS_PATH LOCKED_PATH...\n\n"
399 "Unconditionally remove lock from each LOCKED_PATH.\n"),
402 {"rmtxns", subcommand_rmtxns
, {0}, N_
403 ("usage: svnadmin rmtxns REPOS_PATH TXN_NAME...\n\n"
404 "Delete the named transaction(s).\n"),
407 {"setlog", subcommand_setlog
, {0}, N_
408 ("usage: svnadmin setlog REPOS_PATH -r REVISION FILE\n\n"
409 "Set the log-message on revision REVISION to the contents of FILE. Use\n"
410 "--bypass-hooks to avoid triggering the revision-property-related hooks\n"
411 "(for example, if you do not want an email notification sent\n"
412 "from your post-revprop-change hook, or because the modification of\n"
413 "revision properties has not been enabled in the pre-revprop-change\n"
415 "NOTE: Revision properties are not versioned, so this command will\n"
416 "overwrite the previous log message.\n"),
417 {'r', svnadmin__bypass_hooks
} },
419 {"setrevprop", subcommand_setrevprop
, {0}, N_
420 ("usage: svnadmin setrevprop REPOS_PATH -r REVISION NAME FILE\n\n"
421 "Set the property NAME on revision REVISION to the contents of FILE. Use\n"
422 "--use-pre-revprop-change-hook/--use-post-revprop-change-hook to trigger\n"
423 "the revision property-related hooks (for example, if you want an email\n"
424 "notification sent from your post-revprop-change hook).\n\n"
425 "NOTE: Revision properties are not versioned, so this command will\n"
426 "overwrite the previous value of the property.\n"),
427 {'r', svnadmin__use_pre_revprop_change_hook
,
428 svnadmin__use_post_revprop_change_hook
} },
430 {"setuuid", subcommand_setuuid
, {0}, N_
431 ("usage: svnadmin setuuid REPOS_PATH [NEW_UUID]\n\n"
432 "Reset the repository UUID for the repository located at REPOS_PATH. If\n"
433 "NEW_UUID is provided, use that as the new repository UUID; otherwise,\n"
434 "generate a brand new UUID for the repository.\n"),
437 {"upgrade", subcommand_upgrade
, {0}, N_
438 ("usage: svnadmin upgrade REPOS_PATH\n\n"
439 "Upgrade the repository located at REPOS_PATH to the latest supported\n"
440 "schema version.\n\n"
441 "This functionality is provided as a convenience for repository\n"
442 "administrators who wish to make use of new Subversion functionality\n"
443 "without having to undertake a potentially costly full repository dump\n"
444 "and load operation. As such, the upgrade performs only the minimum\n"
445 "amount of work needed to accomplish this while still maintaining the\n"
446 "integrity of the repository. It does not guarantee the most optimized\n"
447 "repository state as a dump and subsequent load would.\n"),
450 {"verify", subcommand_verify
, {0}, N_
451 ("usage: svnadmin verify REPOS_PATH\n\n"
452 "Verifies the data stored in the repository.\n"),
455 { NULL
, NULL
, {0}, NULL
, {0} }
459 /* Baton for passing option/argument state to a subcommand function. */
460 struct svnadmin_opt_state
462 const char *repository_path
;
463 const char *new_repository_path
; /* hotcopy dest. path */
464 const char *fs_type
; /* --fs-type */
465 svn_boolean_t pre_1_4_compatible
; /* --pre-1.4-compatible */
466 svn_boolean_t pre_1_5_compatible
; /* --pre-1.5-compatible */
467 svn_opt_revision_t start_revision
, end_revision
; /* -r X[:Y] */
468 svn_boolean_t help
; /* --help or -? */
469 svn_boolean_t version
; /* --version */
470 svn_boolean_t incremental
; /* --incremental */
471 svn_boolean_t use_deltas
; /* --deltas */
472 svn_boolean_t use_pre_commit_hook
; /* --use-pre-commit-hook */
473 svn_boolean_t use_post_commit_hook
; /* --use-post-commit-hook */
474 svn_boolean_t use_pre_revprop_change_hook
; /* --use-pre-revprop-change-hook */
475 svn_boolean_t use_post_revprop_change_hook
; /* --use-post-revprop-change-hook */
476 svn_boolean_t quiet
; /* --quiet */
477 svn_boolean_t bdb_txn_nosync
; /* --bdb-txn-nosync */
478 svn_boolean_t bdb_log_keep
; /* --bdb-log-keep */
479 svn_boolean_t clean_logs
; /* --clean-logs */
480 svn_boolean_t bypass_hooks
; /* --bypass-hooks */
481 svn_boolean_t wait
; /* --wait */
482 enum svn_repos_load_uuid uuid_action
; /* --ignore-uuid,
484 const char *parent_dir
;
486 const char *config_dir
; /* Overriding Configuration Directory */
490 /* Set *REVNUM to the revision specified by REVISION (or to
491 SVN_INVALID_REVNUM if that has the type 'unspecified'),
492 possibly making use of the YOUNGEST revision number in REPOS. */
494 get_revnum(svn_revnum_t
*revnum
, const svn_opt_revision_t
*revision
,
495 svn_revnum_t youngest
, svn_repos_t
*repos
, apr_pool_t
*pool
)
497 if (revision
->kind
== svn_opt_revision_number
)
498 *revnum
= revision
->value
.number
;
499 else if (revision
->kind
== svn_opt_revision_head
)
501 else if (revision
->kind
== svn_opt_revision_date
)
502 SVN_ERR(svn_repos_dated_revision
503 (revnum
, repos
, revision
->value
.date
, pool
));
504 else if (revision
->kind
== svn_opt_revision_unspecified
)
505 *revnum
= SVN_INVALID_REVNUM
;
507 return svn_error_create
508 (SVN_ERR_CL_ARG_PARSING_ERROR
, NULL
, _("Invalid revision specifier"));
510 if (*revnum
> youngest
)
511 return svn_error_createf
512 (SVN_ERR_CL_ARG_PARSING_ERROR
, NULL
,
513 _("Revisions must not be greater than the youngest revision (%ld)"),
520 /* This implements `svn_opt_subcommand_t'. */
522 subcommand_create(apr_getopt_t
*os
, void *baton
, apr_pool_t
*pool
)
524 struct svnadmin_opt_state
*opt_state
= baton
;
527 apr_hash_t
*fs_config
= apr_hash_make(pool
);
529 apr_hash_set(fs_config
, SVN_FS_CONFIG_BDB_TXN_NOSYNC
,
531 (opt_state
->bdb_txn_nosync
? "1" : "0"));
533 apr_hash_set(fs_config
, SVN_FS_CONFIG_BDB_LOG_AUTOREMOVE
,
535 (opt_state
->bdb_log_keep
? "0" : "1"));
537 if (opt_state
->fs_type
)
538 apr_hash_set(fs_config
, SVN_FS_CONFIG_FS_TYPE
,
542 if (opt_state
->pre_1_4_compatible
)
543 apr_hash_set(fs_config
, SVN_FS_CONFIG_PRE_1_4_COMPATIBLE
,
547 if (opt_state
->pre_1_5_compatible
)
548 apr_hash_set(fs_config
, SVN_FS_CONFIG_PRE_1_5_COMPATIBLE
,
552 SVN_ERR(svn_config_get_config(&config
, opt_state
->config_dir
, pool
));
553 SVN_ERR(svn_repos_create(&repos
, opt_state
->repository_path
,
555 config
, fs_config
, pool
));
556 svn_fs_set_warning_func(svn_repos_fs(repos
), warning_func
, NULL
);
561 /* This implements `svn_opt_subcommand_t'. */
563 subcommand_deltify(apr_getopt_t
*os
, void *baton
, apr_pool_t
*pool
)
565 struct svnadmin_opt_state
*opt_state
= baton
;
568 svn_revnum_t start
= SVN_INVALID_REVNUM
, end
= SVN_INVALID_REVNUM
;
569 svn_revnum_t youngest
, revision
;
570 apr_pool_t
*subpool
= svn_pool_create(pool
);
572 SVN_ERR(open_repos(&repos
, opt_state
->repository_path
, pool
));
573 fs
= svn_repos_fs(repos
);
574 SVN_ERR(svn_fs_youngest_rev(&youngest
, fs
, pool
));
576 /* Find the revision numbers at which to start and end. */
577 SVN_ERR(get_revnum(&start
, &opt_state
->start_revision
,
578 youngest
, repos
, pool
));
579 SVN_ERR(get_revnum(&end
, &opt_state
->end_revision
,
580 youngest
, repos
, pool
));
582 /* Fill in implied revisions if necessary. */
583 if (start
== SVN_INVALID_REVNUM
)
585 if (end
== SVN_INVALID_REVNUM
)
589 return svn_error_create
590 (SVN_ERR_CL_ARG_PARSING_ERROR
, NULL
,
591 _("First revision cannot be higher than second"));
593 /* Loop over the requested revision range, performing the
594 predecessor deltification on paths changed in each. */
595 for (revision
= start
; revision
<= end
; revision
++)
597 svn_pool_clear(subpool
);
598 SVN_ERR(check_cancel(NULL
));
599 if (! opt_state
->quiet
)
600 SVN_ERR(svn_cmdline_printf(subpool
, _("Deltifying revision %ld..."),
602 SVN_ERR(svn_fs_deltify_revision(fs
, revision
, subpool
));
603 if (! opt_state
->quiet
)
604 SVN_ERR(svn_cmdline_printf(subpool
, _("done.\n")));
606 svn_pool_destroy(subpool
);
612 /* Baton for recode_write(). */
613 struct recode_write_baton
619 /* This implements the 'svn_write_fn_t' interface.
621 Write DATA to ((struct recode_write_baton *) BATON)->out, in the
622 console encoding, using svn_cmdline_fprintf(). DATA is a
623 UTF8-encoded C string, therefore ignore LEN.
625 ### This recoding mechanism might want to be abstracted into
626 ### svn_io.h or svn_cmdline.h, if it proves useful elsewhere. */
627 static svn_error_t
*recode_write(void *baton
,
631 struct recode_write_baton
*rwb
= baton
;
632 svn_pool_clear(rwb
->pool
);
633 return svn_cmdline_fputs(data
, rwb
->out
, rwb
->pool
);
636 /* Create a stream, to write to STD_STREAM, that uses recode_write()
637 to perform UTF-8 to console encoding translation. */
638 static svn_stream_t
*
639 recode_stream_create(FILE *std_stream
, apr_pool_t
*pool
)
641 struct recode_write_baton
*std_stream_rwb
=
642 apr_palloc(pool
, sizeof(struct recode_write_baton
));
644 svn_stream_t
*rw_stream
= svn_stream_create(std_stream_rwb
, pool
);
645 std_stream_rwb
->pool
= svn_pool_create(pool
);
646 std_stream_rwb
->out
= std_stream
;
647 svn_stream_set_write(rw_stream
, recode_write
);
652 /* This implements `svn_opt_subcommand_t'. */
654 subcommand_dump(apr_getopt_t
*os
, void *baton
, apr_pool_t
*pool
)
656 struct svnadmin_opt_state
*opt_state
= baton
;
659 svn_stream_t
*stdout_stream
, *stderr_stream
= NULL
;
660 svn_revnum_t lower
= SVN_INVALID_REVNUM
, upper
= SVN_INVALID_REVNUM
;
661 svn_revnum_t youngest
;
663 SVN_ERR(open_repos(&repos
, opt_state
->repository_path
, pool
));
664 fs
= svn_repos_fs(repos
);
665 SVN_ERR(svn_fs_youngest_rev(&youngest
, fs
, pool
));
667 /* Find the revision numbers at which to start and end. */
668 SVN_ERR(get_revnum(&lower
, &opt_state
->start_revision
,
669 youngest
, repos
, pool
));
670 SVN_ERR(get_revnum(&upper
, &opt_state
->end_revision
,
671 youngest
, repos
, pool
));
673 /* Fill in implied revisions if necessary. */
674 if (lower
== SVN_INVALID_REVNUM
)
679 else if (upper
== SVN_INVALID_REVNUM
)
685 return svn_error_create
686 (SVN_ERR_CL_ARG_PARSING_ERROR
, NULL
,
687 _("First revision cannot be higher than second"));
689 SVN_ERR(create_stdio_stream(&stdout_stream
, apr_file_open_stdout
, pool
));
691 /* Progress feedback goes to STDERR, unless they asked to suppress it. */
692 if (! opt_state
->quiet
)
693 stderr_stream
= recode_stream_create(stderr
, pool
);
695 SVN_ERR(svn_repos_dump_fs2(repos
, stdout_stream
, stderr_stream
,
696 lower
, upper
, opt_state
->incremental
,
697 opt_state
->use_deltas
, check_cancel
, NULL
,
704 /* This implements `svn_opt_subcommand_t'. */
706 subcommand_help(apr_getopt_t
*os
, void *baton
, apr_pool_t
*pool
)
708 struct svnadmin_opt_state
*opt_state
= baton
;
710 _("general usage: svnadmin SUBCOMMAND REPOS_PATH [ARGS & OPTIONS ...]\n"
711 "Type 'svnadmin help <subcommand>' for help on a specific subcommand.\n"
712 "Type 'svnadmin --version' to see the program version and FS modules.\n"
714 "Available subcommands:\n");
716 const char *fs_desc_start
717 = _("The following repository back-end (FS) modules are available:\n\n");
719 svn_stringbuf_t
*version_footer
;
721 version_footer
= svn_stringbuf_create(fs_desc_start
, pool
);
722 SVN_ERR(svn_fs_print_modules(version_footer
, pool
));
724 SVN_ERR(svn_opt_print_help(os
, "svnadmin",
725 opt_state
? opt_state
->version
: FALSE
,
726 FALSE
, version_footer
->data
,
727 header
, cmd_table
, options_table
, NULL
,
734 /* This implements `svn_opt_subcommand_t'. */
736 subcommand_load(apr_getopt_t
*os
, void *baton
, apr_pool_t
*pool
)
738 struct svnadmin_opt_state
*opt_state
= baton
;
740 svn_stream_t
*stdin_stream
, *stdout_stream
= NULL
;
742 SVN_ERR(open_repos(&repos
, opt_state
->repository_path
, pool
));
744 /* Read the stream from STDIN. Users can redirect a file. */
745 SVN_ERR(create_stdio_stream(&stdin_stream
,
746 apr_file_open_stdin
, pool
));
748 /* Progress feedback goes to STDOUT, unless they asked to suppress it. */
749 if (! opt_state
->quiet
)
750 stdout_stream
= recode_stream_create(stdout
, pool
);
752 SVN_ERR(svn_repos_load_fs2(repos
, stdin_stream
, stdout_stream
,
753 opt_state
->uuid_action
, opt_state
->parent_dir
,
754 opt_state
->use_pre_commit_hook
,
755 opt_state
->use_post_commit_hook
,
756 check_cancel
, NULL
, pool
));
762 /* This implements `svn_opt_subcommand_t'. */
764 subcommand_lstxns(apr_getopt_t
*os
, void *baton
, apr_pool_t
*pool
)
766 struct svnadmin_opt_state
*opt_state
= baton
;
769 apr_array_header_t
*txns
;
772 SVN_ERR(open_repos(&repos
, opt_state
->repository_path
, pool
));
773 fs
= svn_repos_fs(repos
);
774 SVN_ERR(svn_fs_list_transactions(&txns
, fs
, pool
));
776 /* Loop, printing revisions. */
777 for (i
= 0; i
< txns
->nelts
; i
++)
779 SVN_ERR(svn_cmdline_printf(pool
, "%s\n",
780 APR_ARRAY_IDX(txns
, i
, const char *)));
787 /* A callback which is called when the recovery starts. */
789 recovery_started(void *baton
)
791 apr_pool_t
*pool
= (apr_pool_t
*)baton
;
793 SVN_ERR(svn_cmdline_printf(pool
,
794 _("Repository lock acquired.\n"
795 "Please wait; recovering the"
796 " repository may take some time...\n")));
797 SVN_ERR(svn_cmdline_fflush(stdout
));
799 /* Enable cancellation signal handlers. */
800 setup_cancellation_signals(signal_handler
);
806 /* This implements `svn_opt_subcommand_t'. */
808 subcommand_recover(apr_getopt_t
*os
, void *baton
, apr_pool_t
*pool
)
810 svn_revnum_t youngest_rev
;
813 struct svnadmin_opt_state
*opt_state
= baton
;
815 /* Restore default signal handlers until after we have acquired the
816 * exclusive lock so that the user interrupt before we actually
817 * touch the repository. */
818 setup_cancellation_signals(SIG_DFL
);
820 err
= svn_repos_recover3(opt_state
->repository_path
, TRUE
,
821 recovery_started
, pool
,
822 check_cancel
, NULL
, pool
);
825 if (! APR_STATUS_IS_EAGAIN(err
->apr_err
))
827 svn_error_clear(err
);
828 if (! opt_state
->wait
)
829 return svn_error_create(SVN_ERR_REPOS_LOCKED
, NULL
,
830 _("Failed to get exclusive repository "
831 "access; perhaps another process\n"
832 "such as httpd, svnserve or svn "
834 SVN_ERR(svn_cmdline_printf(pool
,
835 _("Waiting on repository lock; perhaps"
836 " another process has it open?\n")));
837 SVN_ERR(svn_cmdline_fflush(stdout
));
838 SVN_ERR(svn_repos_recover3(opt_state
->repository_path
, FALSE
,
839 recovery_started
, pool
,
840 check_cancel
, NULL
, pool
));
843 SVN_ERR(svn_cmdline_printf(pool
, _("\nRecovery completed.\n")));
845 /* Since db transactions may have been replayed, it's nice to tell
846 people what the latest revision is. It also proves that the
847 recovery actually worked. */
848 SVN_ERR(open_repos(&repos
, opt_state
->repository_path
, pool
));
849 SVN_ERR(svn_fs_youngest_rev(&youngest_rev
, svn_repos_fs(repos
), pool
));
850 SVN_ERR(svn_cmdline_printf(pool
, _("The latest repos revision is %ld.\n"),
857 /* This implements `svn_opt_subcommand_t'. */
859 list_dblogs(apr_getopt_t
*os
, void *baton
, svn_boolean_t only_unused
,
862 struct svnadmin_opt_state
*opt_state
= baton
;
863 apr_array_header_t
*logfiles
;
866 SVN_ERR(svn_repos_db_logfiles(&logfiles
,
867 opt_state
->repository_path
,
871 /* Loop, printing log files. We append the log paths to the
872 repository path, making sure to return everything to the native
873 style before printing. */
874 for (i
= 0; i
< logfiles
->nelts
; i
++)
876 const char *log_utf8
;
877 log_utf8
= svn_path_join(opt_state
->repository_path
,
878 APR_ARRAY_IDX(logfiles
, i
, const char *),
880 log_utf8
= svn_path_local_style(log_utf8
, pool
);
881 SVN_ERR(svn_cmdline_printf(pool
, "%s\n", log_utf8
));
888 /* This implements `svn_opt_subcommand_t'. */
890 subcommand_list_dblogs(apr_getopt_t
*os
, void *baton
, apr_pool_t
*pool
)
892 SVN_ERR(list_dblogs(os
, baton
, FALSE
, pool
));
897 /* This implements `svn_opt_subcommand_t'. */
899 subcommand_list_unused_dblogs(apr_getopt_t
*os
, void *baton
, apr_pool_t
*pool
)
901 SVN_ERR(list_dblogs(os
, baton
, TRUE
, pool
));
906 /* This implements `svn_opt_subcommand_t'. */
908 subcommand_rmtxns(apr_getopt_t
*os
, void *baton
, apr_pool_t
*pool
)
910 struct svnadmin_opt_state
*opt_state
= baton
;
914 apr_array_header_t
*args
;
916 apr_pool_t
*subpool
= svn_pool_create(pool
);
918 SVN_ERR(open_repos(&repos
, opt_state
->repository_path
, pool
));
919 fs
= svn_repos_fs(repos
);
921 SVN_ERR(svn_opt_parse_all_args(&args
, os
, pool
));
923 /* All the rest of the arguments are transaction names. */
924 for (i
= 0; i
< args
->nelts
; i
++)
926 const char *txn_name
= APR_ARRAY_IDX(args
, i
, const char *);
927 const char *txn_name_utf8
;
930 svn_pool_clear(subpool
);
932 SVN_ERR(svn_utf_cstring_to_utf8(&txn_name_utf8
, txn_name
, subpool
));
934 /* Try to open the txn. If that succeeds, try to abort it. */
935 err
= svn_fs_open_txn(&txn
, fs
, txn_name_utf8
, subpool
);
937 err
= svn_fs_abort_txn(txn
, subpool
);
939 /* If either the open or the abort of the txn fails because that
940 transaction is dead, just try to purge the thing. Else,
941 there was either an error worth reporting, or not error at
943 if (err
&& (err
->apr_err
== SVN_ERR_FS_TRANSACTION_DEAD
))
945 svn_error_clear(err
);
946 err
= svn_fs_purge_txn(fs
, txn_name_utf8
, subpool
);
949 /* If we had a real from the txn open, abort, or purge, we clear
950 that error and just report to the user that we had an issue
951 with this particular txn. */
954 svn_handle_error2(err
, stderr
, FALSE
/* non-fatal */, "svnadmin: ");
955 svn_error_clear(err
);
957 else if (! opt_state
->quiet
)
960 (svn_cmdline_printf(subpool
, _("Transaction '%s' removed.\n"),
965 svn_pool_destroy(subpool
);
971 /* A helper for the 'setrevprop' and 'setlog' commands. Expects
972 OPT_STATE->use_pre_revprop_change_hook and
973 OPT_STATE->use_post_revprop_change_hook to be set appropriately. */
975 set_revprop(const char *prop_name
, const char *filename
,
976 struct svnadmin_opt_state
*opt_state
, apr_pool_t
*pool
)
979 svn_string_t
*prop_value
= svn_string_create("", pool
);
980 svn_stringbuf_t
*file_contents
;
981 const char *filename_utf8
;
983 SVN_ERR(svn_utf_cstring_to_utf8(&filename_utf8
, filename
, pool
));
984 filename_utf8
= svn_path_internal_style(filename_utf8
, pool
);
986 SVN_ERR(svn_stringbuf_from_file(&file_contents
, filename_utf8
, pool
));
988 prop_value
->data
= file_contents
->data
;
989 prop_value
->len
= file_contents
->len
;
991 SVN_ERR(svn_subst_translate_string(&prop_value
, prop_value
, NULL
, pool
));
993 /* Open the filesystem */
994 SVN_ERR(open_repos(&repos
, opt_state
->repository_path
, pool
));
996 /* If we are bypassing the hooks system, we just hit the filesystem
998 if (opt_state
->use_pre_revprop_change_hook
||
999 opt_state
->use_post_revprop_change_hook
)
1001 SVN_ERR(svn_repos_fs_change_rev_prop3
1002 (repos
, opt_state
->start_revision
.value
.number
,
1003 NULL
, prop_name
, prop_value
,
1004 opt_state
->use_pre_revprop_change_hook
,
1005 opt_state
->use_post_revprop_change_hook
, NULL
, NULL
, pool
));
1009 svn_fs_t
*fs
= svn_repos_fs(repos
);
1010 SVN_ERR(svn_fs_change_rev_prop
1011 (fs
, opt_state
->start_revision
.value
.number
,
1012 prop_name
, prop_value
, pool
));
1015 return SVN_NO_ERROR
;
1019 /* This implements `svn_opt_subcommand_t'. */
1020 static svn_error_t
*
1021 subcommand_setrevprop(apr_getopt_t
*os
, void *baton
, apr_pool_t
*pool
)
1023 struct svnadmin_opt_state
*opt_state
= baton
;
1024 apr_array_header_t
*args
;
1026 if (opt_state
->start_revision
.kind
!= svn_opt_revision_number
)
1027 return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR
, NULL
,
1028 _("Missing revision"));
1029 else if (opt_state
->end_revision
.kind
!= svn_opt_revision_unspecified
)
1030 return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR
, NULL
,
1031 _("Only one revision allowed"));
1033 SVN_ERR(svn_opt_parse_all_args(&args
, os
, pool
));
1035 if (args
->nelts
!= 2)
1036 return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR
, NULL
,
1037 _("Exactly one property name and one file "
1038 "argument required"));
1040 return set_revprop(APR_ARRAY_IDX(args
, 0, const char *),
1041 APR_ARRAY_IDX(args
, 1, const char *),
1046 /* This implements `svn_opt_subcommand_t'. */
1047 static svn_error_t
*
1048 subcommand_setuuid(apr_getopt_t
*os
, void *baton
, apr_pool_t
*pool
)
1050 struct svnadmin_opt_state
*opt_state
= baton
;
1051 apr_array_header_t
*args
;
1054 const char *uuid
= NULL
;
1056 SVN_ERR(svn_opt_parse_all_args(&args
, os
, pool
));
1058 if (args
->nelts
> 1)
1059 return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR
, NULL
, NULL
);
1060 if (args
->nelts
== 1)
1061 uuid
= APR_ARRAY_IDX(args
, 0, const char *);
1063 SVN_ERR(open_repos(&repos
, opt_state
->repository_path
, pool
));
1064 fs
= svn_repos_fs(repos
);
1065 return svn_fs_set_uuid(fs
, uuid
, pool
);
1069 /* This implements `svn_opt_subcommand_t'. */
1070 static svn_error_t
*
1071 subcommand_setlog(apr_getopt_t
*os
, void *baton
, apr_pool_t
*pool
)
1073 struct svnadmin_opt_state
*opt_state
= baton
;
1074 apr_array_header_t
*args
;
1076 if (opt_state
->start_revision
.kind
!= svn_opt_revision_number
)
1077 return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR
, NULL
,
1078 _("Missing revision"));
1079 else if (opt_state
->end_revision
.kind
!= svn_opt_revision_unspecified
)
1080 return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR
, NULL
,
1081 _("Only one revision allowed"));
1083 SVN_ERR(svn_opt_parse_all_args(&args
, os
, pool
));
1085 if (args
->nelts
!= 1)
1086 return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR
, NULL
,
1087 _("Exactly one file argument required"));
1089 /* set_revprop() responds only to pre-/post-revprop-change opts. */
1090 if (!opt_state
->bypass_hooks
)
1092 opt_state
->use_pre_revprop_change_hook
= TRUE
;
1093 opt_state
->use_post_revprop_change_hook
= TRUE
;
1096 return set_revprop(SVN_PROP_REVISION_LOG
,
1097 APR_ARRAY_IDX(args
, 0, const char *),
1102 /* This implements `svn_opt_subcommand_t'. */
1103 static svn_error_t
*
1104 subcommand_verify(apr_getopt_t
*os
, void *baton
, apr_pool_t
*pool
)
1106 struct svnadmin_opt_state
*opt_state
= baton
;
1107 svn_stream_t
*stderr_stream
;
1110 svn_revnum_t youngest
, lower
, upper
;
1112 SVN_ERR(open_repos(&repos
, opt_state
->repository_path
, pool
));
1113 fs
= svn_repos_fs(repos
);
1114 SVN_ERR(svn_fs_youngest_rev(&youngest
, fs
, pool
));
1116 /* Find the revision numbers at which to start and end. */
1117 SVN_ERR(get_revnum(&lower
, &opt_state
->start_revision
,
1118 youngest
, repos
, pool
));
1119 SVN_ERR(get_revnum(&upper
, &opt_state
->end_revision
,
1120 youngest
, repos
, pool
));
1122 if (upper
== SVN_INVALID_REVNUM
)
1127 if (opt_state
->quiet
)
1128 stderr_stream
= NULL
;
1130 stderr_stream
= recode_stream_create(stderr
, pool
);
1132 SVN_ERR(open_repos(&repos
, opt_state
->repository_path
, pool
));
1133 return svn_repos_verify_fs(repos
, stderr_stream
, lower
, upper
,
1134 check_cancel
, NULL
, pool
);
1138 /* This implements `svn_opt_subcommand_t'. */
1140 subcommand_hotcopy(apr_getopt_t
*os
, void *baton
, apr_pool_t
*pool
)
1142 struct svnadmin_opt_state
*opt_state
= baton
;
1144 SVN_ERR(svn_repos_hotcopy(opt_state
->repository_path
,
1145 opt_state
->new_repository_path
,
1146 opt_state
->clean_logs
,
1149 return SVN_NO_ERROR
;
1153 static svn_error_t
*
1154 subcommand_lslocks(apr_getopt_t
*os
, void *baton
, apr_pool_t
*pool
)
1156 struct svnadmin_opt_state
*opt_state
= baton
;
1157 apr_array_header_t
*targets
;
1159 const char *fs_path
= "/";
1162 apr_hash_index_t
*hi
;
1164 SVN_ERR(svn_opt_args_to_target_array2(&targets
, os
,
1165 apr_array_make(pool
, 0,
1166 sizeof(const char *)),
1168 if (targets
->nelts
> 1)
1169 return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR
, 0, NULL
);
1171 fs_path
= APR_ARRAY_IDX(targets
, 0, const char *);
1173 SVN_ERR(open_repos(&repos
, opt_state
->repository_path
, pool
));
1174 fs
= svn_repos_fs(repos
);
1176 /* Fetch all locks on or below the root directory. */
1177 SVN_ERR(svn_repos_fs_get_locks(&locks
, repos
, fs_path
, NULL
, NULL
, pool
));
1179 for (hi
= apr_hash_first(pool
, locks
); hi
; hi
= apr_hash_next(hi
))
1183 const char *path
, *cr_date
, *exp_date
= "";
1185 int comment_lines
= 0;
1187 apr_hash_this(hi
, &key
, NULL
, &val
);
1191 cr_date
= svn_time_to_human_cstring(lock
->creation_date
, pool
);
1193 if (lock
->expiration_date
)
1194 exp_date
= svn_time_to_human_cstring(lock
->expiration_date
, pool
);
1197 comment_lines
= svn_cstring_count_newlines(lock
->comment
) + 1;
1199 SVN_ERR(svn_cmdline_printf(pool
, _("Path: %s\n"), path
));
1200 SVN_ERR(svn_cmdline_printf(pool
, _("UUID Token: %s\n"), lock
->token
));
1201 SVN_ERR(svn_cmdline_printf(pool
, _("Owner: %s\n"), lock
->owner
));
1202 SVN_ERR(svn_cmdline_printf(pool
, _("Created: %s\n"), cr_date
));
1203 SVN_ERR(svn_cmdline_printf(pool
, _("Expires: %s\n"), exp_date
));
1204 SVN_ERR(svn_cmdline_printf(pool
, (comment_lines
!= 1)
1205 ? _("Comment (%i lines):\n%s\n\n")
1206 : _("Comment (%i line):\n%s\n\n"),
1208 lock
->comment
? lock
->comment
: ""));
1211 return SVN_NO_ERROR
;
1216 static svn_error_t
*
1217 subcommand_rmlocks(apr_getopt_t
*os
, void *baton
, apr_pool_t
*pool
)
1219 struct svnadmin_opt_state
*opt_state
= baton
;
1222 svn_fs_access_t
*access
;
1224 apr_array_header_t
*args
;
1226 const char *username
;
1227 apr_pool_t
*subpool
= svn_pool_create(pool
);
1229 SVN_ERR(open_repos(&repos
, opt_state
->repository_path
, pool
));
1230 fs
= svn_repos_fs(repos
);
1232 /* svn_fs_unlock() demands that some username be associated with the
1233 filesystem, so just use the UID of the person running 'svnadmin'.*/
1234 username
= svn_user_get_name(pool
);
1236 username
= "administrator";
1238 /* Create an access context describing the current user. */
1239 SVN_ERR(svn_fs_create_access(&access
, username
, pool
));
1241 /* Attach the access context to the filesystem. */
1242 SVN_ERR(svn_fs_set_access(fs
, access
));
1244 /* Parse out any options. */
1245 SVN_ERR(svn_opt_parse_all_args(&args
, os
, pool
));
1247 /* Our usage requires at least one FS path. */
1248 if (args
->nelts
== 0)
1249 return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR
, 0,
1250 _("No paths to unlock provided"));
1252 /* All the rest of the arguments are paths from which to remove locks. */
1253 for (i
= 0; i
< args
->nelts
; i
++)
1255 const char *lock_path
= APR_ARRAY_IDX(args
, i
, const char *);
1256 const char *lock_path_utf8
;
1259 SVN_ERR(svn_utf_cstring_to_utf8(&lock_path_utf8
, lock_path
, subpool
));
1261 /* Fetch the path's svn_lock_t. */
1262 err
= svn_fs_get_lock(&lock
, fs
, lock_path_utf8
, subpool
);
1267 SVN_ERR(svn_cmdline_printf(subpool
,
1268 _("Path '%s' isn't locked.\n"),
1273 /* Now forcibly destroy the lock. */
1274 err
= svn_fs_unlock(fs
, lock_path_utf8
,
1275 lock
->token
, 1 /* force */, subpool
);
1279 SVN_ERR(svn_cmdline_printf(subpool
,
1280 _("Removed lock on '%s'.\n"), lock
->path
));
1285 /* Print the error, but move on to the next lock. */
1286 svn_handle_error2(err
, stderr
, FALSE
/* non-fatal */, "svnadmin: ");
1287 svn_error_clear(err
);
1290 svn_pool_clear(subpool
);
1293 svn_pool_destroy(subpool
);
1294 return SVN_NO_ERROR
;
1298 /* A callback which is called when the upgrade starts. */
1299 static svn_error_t
*
1300 upgrade_started(void *baton
)
1302 apr_pool_t
*pool
= (apr_pool_t
*)baton
;
1304 SVN_ERR(svn_cmdline_printf(pool
,
1305 _("Repository lock acquired.\n"
1306 "Please wait; upgrading the"
1307 " repository may take some time...\n")));
1308 SVN_ERR(svn_cmdline_fflush(stdout
));
1310 /* Enable cancellation signal handlers. */
1311 setup_cancellation_signals(signal_handler
);
1313 return SVN_NO_ERROR
;
1317 /* This implements `svn_opt_subcommand_t'. */
1318 static svn_error_t
*
1319 subcommand_upgrade(apr_getopt_t
*os
, void *baton
, apr_pool_t
*pool
)
1322 struct svnadmin_opt_state
*opt_state
= baton
;
1324 /* Restore default signal handlers. */
1325 setup_cancellation_signals(SIG_DFL
);
1327 err
= svn_repos_upgrade(opt_state
->repository_path
, TRUE
,
1328 upgrade_started
, pool
, pool
);
1331 if (APR_STATUS_IS_EAGAIN(err
->apr_err
))
1333 svn_error_clear(err
);
1335 if (! opt_state
->wait
)
1336 return svn_error_create(SVN_ERR_REPOS_LOCKED
, NULL
,
1337 _("Failed to get exclusive repository "
1338 "access; perhaps another process\n"
1339 "such as httpd, svnserve or svn "
1341 SVN_ERR(svn_cmdline_printf(pool
,
1342 _("Waiting on repository lock; perhaps"
1343 " another process has it open?\n")));
1344 SVN_ERR(svn_cmdline_fflush(stdout
));
1345 SVN_ERR(svn_repos_upgrade(opt_state
->repository_path
, FALSE
,
1346 upgrade_started
, pool
, pool
));
1348 else if (err
->apr_err
== SVN_ERR_FS_UNSUPPORTED_UPGRADE
)
1350 return svn_error_quick_wrap
1351 (err
, _("Upgrade of this repository's underlying versioned "
1352 "filesystem is not supported; consider "
1353 "dumping and loading the data elsewhere"));
1355 else if (err
->apr_err
== SVN_ERR_REPOS_UNSUPPORTED_UPGRADE
)
1357 return svn_error_quick_wrap
1358 (err
, _("Upgrade of this repository is not supported; consider "
1359 "dumping and loading the data elsewhere"));
1364 SVN_ERR(svn_cmdline_printf(pool
, _("\nUpgrade completed.\n")));
1365 return SVN_NO_ERROR
;
1373 main(int argc
, const char *argv
[])
1376 apr_status_t apr_err
;
1377 apr_allocator_t
*allocator
;
1380 const svn_opt_subcommand_desc_t
*subcommand
= NULL
;
1381 struct svnadmin_opt_state opt_state
;
1384 apr_array_header_t
*received_opts
;
1387 /* Initialize the app. */
1388 if (svn_cmdline_init("svnadmin", stderr
) != EXIT_SUCCESS
)
1389 return EXIT_FAILURE
;
1391 /* Create our top-level pool. Use a seperate mutexless allocator,
1392 * given this application is single threaded.
1394 if (apr_allocator_create(&allocator
))
1395 return EXIT_FAILURE
;
1397 apr_allocator_max_free_set(allocator
, SVN_ALLOCATOR_RECOMMENDED_MAX_FREE
);
1399 pool
= svn_pool_create_ex(NULL
, allocator
);
1400 apr_allocator_owner_set(allocator
, pool
);
1402 received_opts
= apr_array_make(pool
, SVN_OPT_MAX_OPTIONS
, sizeof(int));
1404 /* Check library versions */
1405 err
= check_lib_versions();
1407 return svn_cmdline_handle_exit_error(err
, pool
, "svnadmin: ");
1409 /* Initialize the FS library. */
1410 err
= svn_fs_initialize(pool
);
1412 return svn_cmdline_handle_exit_error(err
, pool
, "svnadmin: ");
1416 subcommand_help(NULL
, NULL
, pool
);
1417 svn_pool_destroy(pool
);
1418 return EXIT_FAILURE
;
1421 /* Initialize opt_state. */
1422 memset(&opt_state
, 0, sizeof(opt_state
));
1423 opt_state
.start_revision
.kind
= svn_opt_revision_unspecified
;
1424 opt_state
.end_revision
.kind
= svn_opt_revision_unspecified
;
1426 /* Parse options. */
1427 err
= svn_cmdline__getopt_init(&os
, argc
, argv
, pool
);
1429 return svn_cmdline_handle_exit_error(err
, pool
, "svnadmin: ");
1435 const char *opt_arg
;
1436 const char *utf8_opt_arg
;
1438 /* Parse the next option. */
1439 apr_err
= apr_getopt_long(os
, options_table
, &opt_id
, &opt_arg
);
1440 if (APR_STATUS_IS_EOF(apr_err
))
1444 subcommand_help(NULL
, NULL
, pool
);
1445 svn_pool_destroy(pool
);
1446 return EXIT_FAILURE
;
1449 /* Stash the option code in an array before parsing it. */
1450 APR_ARRAY_PUSH(received_opts
, int) = opt_id
;
1455 if (opt_state
.start_revision
.kind
!= svn_opt_revision_unspecified
)
1457 err
= svn_error_create
1458 (SVN_ERR_CL_ARG_PARSING_ERROR
, NULL
,
1459 _("Multiple revision arguments encountered; "
1460 "try '-r N:M' instead of '-r N -r M'"));
1461 return svn_cmdline_handle_exit_error(err
, pool
, "svnadmin: ");
1463 if (svn_opt_parse_revision(&(opt_state
.start_revision
),
1464 &(opt_state
.end_revision
),
1465 opt_arg
, pool
) != 0)
1467 err
= svn_utf_cstring_to_utf8(&utf8_opt_arg
, opt_arg
,
1471 err
= svn_error_createf
1472 (SVN_ERR_CL_ARG_PARSING_ERROR
, NULL
,
1473 _("Syntax error in revision argument '%s'"),
1475 return svn_cmdline_handle_exit_error(err
, pool
, "svnadmin: ");
1480 opt_state
.quiet
= TRUE
;
1484 opt_state
.help
= TRUE
;
1486 case svnadmin__version
:
1487 opt_state
.version
= TRUE
;
1489 case svnadmin__incremental
:
1490 opt_state
.incremental
= TRUE
;
1492 case svnadmin__deltas
:
1493 opt_state
.use_deltas
= TRUE
;
1495 case svnadmin__ignore_uuid
:
1496 opt_state
.uuid_action
= svn_repos_load_uuid_ignore
;
1498 case svnadmin__force_uuid
:
1499 opt_state
.uuid_action
= svn_repos_load_uuid_force
;
1501 case svnadmin__pre_1_4_compatible
:
1502 opt_state
.pre_1_4_compatible
= TRUE
;
1504 case svnadmin__pre_1_5_compatible
:
1505 opt_state
.pre_1_5_compatible
= TRUE
;
1507 case svnadmin__fs_type
:
1508 err
= svn_utf_cstring_to_utf8(&opt_state
.fs_type
, opt_arg
, pool
);
1510 return svn_cmdline_handle_exit_error(err
, pool
, "svnadmin: ");
1512 case svnadmin__parent_dir
:
1513 err
= svn_utf_cstring_to_utf8(&opt_state
.parent_dir
, opt_arg
,
1516 return svn_cmdline_handle_exit_error(err
, pool
, "svnadmin: ");
1517 opt_state
.parent_dir
1518 = svn_path_internal_style(opt_state
.parent_dir
, pool
);
1520 case svnadmin__use_pre_commit_hook
:
1521 opt_state
.use_pre_commit_hook
= TRUE
;
1523 case svnadmin__use_post_commit_hook
:
1524 opt_state
.use_post_commit_hook
= TRUE
;
1526 case svnadmin__use_pre_revprop_change_hook
:
1527 opt_state
.use_pre_revprop_change_hook
= TRUE
;
1529 case svnadmin__use_post_revprop_change_hook
:
1530 opt_state
.use_post_revprop_change_hook
= TRUE
;
1532 case svnadmin__bdb_txn_nosync
:
1533 opt_state
.bdb_txn_nosync
= TRUE
;
1535 case svnadmin__bdb_log_keep
:
1536 opt_state
.bdb_log_keep
= TRUE
;
1538 case svnadmin__bypass_hooks
:
1539 opt_state
.bypass_hooks
= TRUE
;
1541 case svnadmin__clean_logs
:
1542 opt_state
.clean_logs
= TRUE
;
1544 case svnadmin__config_dir
:
1545 opt_state
.config_dir
=
1546 apr_pstrdup(pool
, svn_path_canonicalize(opt_arg
, pool
));
1548 case svnadmin__wait
:
1549 opt_state
.wait
= TRUE
;
1553 subcommand_help(NULL
, NULL
, pool
);
1554 svn_pool_destroy(pool
);
1555 return EXIT_FAILURE
;
1557 } /* close `switch' */
1558 } /* close `while' */
1560 /* If the user asked for help, then the rest of the arguments are
1561 the names of subcommands to get help on (if any), or else they're
1562 just typos/mistakes. Whatever the case, the subcommand to
1563 actually run is subcommand_help(). */
1565 subcommand
= svn_opt_get_canonical_subcommand(cmd_table
, "help");
1567 /* If we're not running the `help' subcommand, then look for a
1568 subcommand in the first argument. */
1569 if (subcommand
== NULL
)
1571 if (os
->ind
>= os
->argc
)
1573 if (opt_state
.version
)
1575 /* Use the "help" subcommand to handle the "--version" option. */
1576 static const svn_opt_subcommand_desc_t pseudo_cmd
=
1577 { "--version", subcommand_help
, {0}, "",
1578 {svnadmin__version
, /* must accept its own option */
1581 subcommand
= &pseudo_cmd
;
1586 (svn_cmdline_fprintf(stderr
, pool
,
1587 _("subcommand argument required\n")));
1588 subcommand_help(NULL
, NULL
, pool
);
1589 svn_pool_destroy(pool
);
1590 return EXIT_FAILURE
;
1595 const char *first_arg
= os
->argv
[os
->ind
++];
1596 subcommand
= svn_opt_get_canonical_subcommand(cmd_table
, first_arg
);
1597 if (subcommand
== NULL
)
1599 const char* first_arg_utf8
;
1600 err
= svn_utf_cstring_to_utf8(&first_arg_utf8
, first_arg
, pool
);
1602 return svn_cmdline_handle_exit_error(err
, pool
, "svnadmin: ");
1604 (svn_cmdline_fprintf(stderr
, pool
,
1605 _("Unknown command: '%s'\n"),
1607 subcommand_help(NULL
, NULL
, pool
);
1608 svn_pool_destroy(pool
);
1609 return EXIT_FAILURE
;
1614 /* If there's a second argument, it's probably the repository.
1615 Every subcommand except `help' requires one, so we parse it out
1616 here and store it in opt_state. */
1617 if (subcommand
->cmd_func
!= subcommand_help
)
1619 err
= parse_local_repos_path(os
,
1620 &(opt_state
.repository_path
),
1623 return svn_cmdline_handle_exit_error(err
, pool
, "svnadmin: ");
1627 /* If command is hot copy the third argument will be the new
1629 if (subcommand
->cmd_func
== subcommand_hotcopy
)
1631 err
= parse_local_repos_path(os
,
1632 &(opt_state
.new_repository_path
),
1635 return svn_cmdline_handle_exit_error(err
, pool
, "svnadmin: ");
1638 /* Check that the subcommand wasn't passed any inappropriate options. */
1639 for (i
= 0; i
< received_opts
->nelts
; i
++)
1641 opt_id
= APR_ARRAY_IDX(received_opts
, i
, int);
1643 /* All commands implicitly accept --help, so just skip over this
1644 when we see it. Note that we don't want to include this option
1645 in their "accepted options" list because it would be awfully
1646 redundant to display it in every commands' help text. */
1647 if (opt_id
== 'h' || opt_id
== '?')
1650 if (! svn_opt_subcommand_takes_option(subcommand
, opt_id
))
1653 const apr_getopt_option_t
*badopt
=
1654 svn_opt_get_option_from_code(opt_id
, options_table
);
1655 svn_opt_format_option(&optstr
, badopt
, FALSE
, pool
);
1656 if (subcommand
->name
[0] == '-')
1657 subcommand_help(NULL
, NULL
, pool
);
1660 (svn_cmdline_fprintf
1661 (stderr
, pool
, _("Subcommand '%s' doesn't accept option '%s'\n"
1662 "Type 'svnadmin help %s' for usage.\n"),
1663 subcommand
->name
, optstr
, subcommand
->name
));
1664 svn_pool_destroy(pool
);
1665 return EXIT_FAILURE
;
1669 /* Set up our cancellation support. */
1670 setup_cancellation_signals(signal_handler
);
1673 /* Disable SIGPIPE generation for the platforms that have it. */
1674 apr_signal(SIGPIPE
, SIG_IGN
);
1678 /* Disable SIGXFSZ generation for the platforms that have it, otherwise
1679 * working with large files when compiled against an APR that doesn't have
1680 * large file support will crash the program, which is uncool. */
1681 apr_signal(SIGXFSZ
, SIG_IGN
);
1684 /* Run the subcommand. */
1685 err
= (*subcommand
->cmd_func
)(os
, &opt_state
, pool
);
1688 /* For argument-related problems, suggest using the 'help'
1690 if (err
->apr_err
== SVN_ERR_CL_INSUFFICIENT_ARGS
1691 || err
->apr_err
== SVN_ERR_CL_ARG_PARSING_ERROR
)
1693 err
= svn_error_quick_wrap(err
,
1694 _("Try 'svnadmin help' for more info"));
1696 return svn_cmdline_handle_exit_error(err
, pool
, "svnadmin: ");
1700 svn_pool_destroy(pool
);
1701 /* Ensure that everything is written to stdout, so the user will
1702 see any print errors. */
1703 err
= svn_cmdline_fflush(stdout
);
1706 svn_handle_error2(err
, stderr
, FALSE
, "svnadmin: ");
1707 svn_error_clear(err
);
1708 return EXIT_FAILURE
;
1710 return EXIT_SUCCESS
;
1715 /* This implements `svn_opt_subcommand_t'. */
1716 static svn_error_t
*
1717 subcommand_crashtest(apr_getopt_t
*os
, void *baton
, apr_pool_t
*pool
)
1719 struct svnadmin_opt_state
*opt_state
= baton
;
1722 SVN_ERR(open_repos(&repos
, opt_state
->repository_path
, pool
));
1725 return SVN_NO_ERROR
;