2 * svnadmin.c: Subversion server administration tool main file.
4 * ====================================================================
5 * Licensed to the Apache Software Foundation (ASF) under one
6 * or more contributor license agreements. See the NOTICE file
7 * distributed with this work for additional information
8 * regarding copyright ownership. The ASF licenses this file
9 * to you under the Apache License, Version 2.0 (the
10 * "License"); you may not use this file except in compliance
11 * with the License. You may obtain a copy of the License at
13 * http://www.apache.org/licenses/LICENSE-2.0
15 * Unless required by applicable law or agreed to in writing,
16 * software distributed under the License is distributed on an
17 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
18 * KIND, either express or implied. See the License for the
19 * specific language governing permissions and limitations
21 * ====================================================================
25 #include <apr_file_io.h>
26 #include <apr_signal.h>
29 #include "svn_pools.h"
30 #include "svn_cmdline.h"
31 #include "svn_error.h"
34 #include "svn_subst.h"
35 #include "svn_dirent_uri.h"
37 #include "svn_config.h"
38 #include "svn_repos.h"
39 #include "svn_cache_config.h"
40 #include "svn_version.h"
41 #include "svn_props.h"
46 #include "private/svn_opt_private.h"
47 #include "private/svn_subr_private.h"
48 #include "private/svn_cmdline_private.h"
50 #include "svn_private_config.h"
55 /* A flag to see if we've been cancelled by the client or not. */
56 static volatile sig_atomic_t cancelled = FALSE;
58 /* A signal handler to support cancellation. */
60 signal_handler(int signum)
62 apr_signal(signum, SIG_IGN);
67 /* A helper to set up the cancellation signal handlers. */
69 setup_cancellation_signals(void (*handler)(int signum))
71 apr_signal(SIGINT, handler);
73 /* SIGBREAK is a Win32 specific signal generated by ctrl-break. */
74 apr_signal(SIGBREAK, handler);
77 apr_signal(SIGHUP, handler);
80 apr_signal(SIGTERM, handler);
85 /* Our cancellation callback. */
87 check_cancel(void *baton)
90 return svn_error_create(SVN_ERR_CANCELLED, NULL, _("Caught signal"));
96 /* Custom filesystem warning function. */
98 warning_func(void *baton,
103 svn_handle_error2(err, stderr, FALSE, "svnadmin: ");
107 /* Helper to open a repository and set a warning func (so we don't
108 * SEGFAULT when libsvn_fs's default handler gets run). */
110 open_repos(svn_repos_t **repos,
114 /* construct FS configuration parameters: enable caches for r/o data */
115 apr_hash_t *fs_config = apr_hash_make(pool);
116 svn_hash_sets(fs_config, SVN_FS_CONFIG_FSFS_CACHE_DELTAS, "1");
117 svn_hash_sets(fs_config, SVN_FS_CONFIG_FSFS_CACHE_FULLTEXTS, "1");
118 svn_hash_sets(fs_config, SVN_FS_CONFIG_FSFS_CACHE_REVPROPS, "2");
119 svn_hash_sets(fs_config, SVN_FS_CONFIG_FSFS_CACHE_NS,
120 svn_uuid_generate(pool));
122 /* now, open the requested repository */
123 SVN_ERR(svn_repos_open2(repos, path, fs_config, pool));
124 svn_fs_set_warning_func(svn_repos_fs(*repos), warning_func, NULL);
129 /* Version compatibility check */
131 check_lib_versions(void)
133 static const svn_version_checklist_t checklist[] =
135 { "svn_subr", svn_subr_version },
136 { "svn_repos", svn_repos_version },
137 { "svn_fs", svn_fs_version },
138 { "svn_delta", svn_delta_version },
141 SVN_VERSION_DEFINE(my_version);
143 return svn_ver_check_list2(&my_version, checklist, svn_ver_equal);
150 static svn_opt_subcommand_t
151 subcommand_crashtest,
159 subcommand_list_dblogs,
160 subcommand_list_unused_dblogs,
169 subcommand_setrevprop,
175 enum svnadmin__cmdline_options_t
177 svnadmin__version = SVN_OPT_FIRST_LONGOPT_ID,
178 svnadmin__incremental,
180 svnadmin__ignore_uuid,
181 svnadmin__force_uuid,
183 svnadmin__parent_dir,
184 svnadmin__bdb_txn_nosync,
185 svnadmin__bdb_log_keep,
186 svnadmin__config_dir,
187 svnadmin__bypass_hooks,
188 svnadmin__bypass_prop_validation,
189 svnadmin__use_pre_commit_hook,
190 svnadmin__use_post_commit_hook,
191 svnadmin__use_pre_revprop_change_hook,
192 svnadmin__use_post_revprop_change_hook,
193 svnadmin__clean_logs,
195 svnadmin__pre_1_4_compatible,
196 svnadmin__pre_1_5_compatible,
197 svnadmin__pre_1_6_compatible,
198 svnadmin__compatible_version
201 /* Option codes and descriptions.
203 * The entire list must be terminated with an entry of nulls.
205 static const apr_getopt_option_t options_table[] =
208 N_("show help on a subcommand")},
211 N_("show help on a subcommand")},
213 {"version", svnadmin__version, 0,
214 N_("show program version information")},
217 N_("specify revision number ARG (or X:Y range)")},
219 {"transaction", 't', 1,
220 N_("specify transaction name ARG")},
222 {"incremental", svnadmin__incremental, 0,
223 N_("dump or hotcopy incrementally")},
225 {"deltas", svnadmin__deltas, 0,
226 N_("use deltas in dump output")},
228 {"bypass-hooks", svnadmin__bypass_hooks, 0,
229 N_("bypass the repository hook system")},
231 {"bypass-prop-validation", svnadmin__bypass_prop_validation, 0,
232 N_("bypass property validation logic")},
235 N_("no progress (only errors) to stderr")},
237 {"ignore-uuid", svnadmin__ignore_uuid, 0,
238 N_("ignore any repos UUID found in the stream")},
240 {"force-uuid", svnadmin__force_uuid, 0,
241 N_("set repos UUID to that found in stream, if any")},
243 {"fs-type", svnadmin__fs_type, 1,
244 N_("type of repository: 'fsfs' (default) or 'bdb'")},
246 {"parent-dir", svnadmin__parent_dir, 1,
247 N_("load at specified directory in repository")},
249 {"bdb-txn-nosync", svnadmin__bdb_txn_nosync, 0,
250 N_("disable fsync at transaction commit [Berkeley DB]")},
252 {"bdb-log-keep", svnadmin__bdb_log_keep, 0,
253 N_("disable automatic log file removal [Berkeley DB]")},
255 {"config-dir", svnadmin__config_dir, 1,
256 N_("read user configuration files from directory ARG")},
258 {"clean-logs", svnadmin__clean_logs, 0,
259 N_("remove redundant Berkeley DB log files\n"
260 " from source repository [Berkeley DB]")},
262 {"use-pre-commit-hook", svnadmin__use_pre_commit_hook, 0,
263 N_("call pre-commit hook before committing revisions")},
265 {"use-post-commit-hook", svnadmin__use_post_commit_hook, 0,
266 N_("call post-commit hook after committing revisions")},
268 {"use-pre-revprop-change-hook", svnadmin__use_pre_revprop_change_hook, 0,
269 N_("call hook before changing revision property")},
271 {"use-post-revprop-change-hook", svnadmin__use_post_revprop_change_hook, 0,
272 N_("call hook after changing revision property")},
274 {"wait", svnadmin__wait, 0,
275 N_("wait instead of exit if the repository is in\n"
276 " use by another process")},
278 {"pre-1.4-compatible", svnadmin__pre_1_4_compatible, 0,
279 N_("deprecated; see --compatible-version")},
281 {"pre-1.5-compatible", svnadmin__pre_1_5_compatible, 0,
282 N_("deprecated; see --compatible-version")},
284 {"pre-1.6-compatible", svnadmin__pre_1_6_compatible, 0,
285 N_("deprecated; see --compatible-version")},
287 {"memory-cache-size", 'M', 1,
288 N_("size of the extra in-memory cache in MB used to\n"
289 " minimize redundant operations. Default: 16.\n"
290 " [used for FSFS repositories only]")},
292 {"compatible-version", svnadmin__compatible_version, 1,
293 N_("use repository format compatible with Subversion\n"
294 " version ARG (\"1.5.5\", \"1.7\", etc.)")},
296 {"file", 'F', 1, N_("read repository paths from file ARG")},
302 /* Array of available subcommands.
303 * The entire list must be terminated with an entry of nulls.
305 static const svn_opt_subcommand_desc2_t cmd_table[] =
307 {"crashtest", subcommand_crashtest, {0}, N_
308 ("usage: svnadmin crashtest REPOS_PATH\n\n"
309 "Open the repository at REPOS_PATH, then abort, thus simulating\n"
310 "a process that crashes while holding an open repository handle.\n"),
313 {"create", subcommand_create, {0}, N_
314 ("usage: svnadmin create REPOS_PATH\n\n"
315 "Create a new, empty repository at REPOS_PATH.\n"),
316 {svnadmin__bdb_txn_nosync, svnadmin__bdb_log_keep,
317 svnadmin__config_dir, svnadmin__fs_type, svnadmin__compatible_version,
318 svnadmin__pre_1_4_compatible, svnadmin__pre_1_5_compatible,
319 svnadmin__pre_1_6_compatible
322 {"deltify", subcommand_deltify, {0}, N_
323 ("usage: svnadmin deltify [-r LOWER[:UPPER]] REPOS_PATH\n\n"
324 "Run over the requested revision range, performing predecessor delti-\n"
325 "fication on the paths changed in those revisions. Deltification in\n"
326 "essence compresses the repository by only storing the differences or\n"
327 "delta from the preceding revision. If no revisions are specified,\n"
328 "this will simply deltify the HEAD revision.\n"),
331 {"dump", subcommand_dump, {0}, N_
332 ("usage: svnadmin dump REPOS_PATH [-r LOWER[:UPPER] [--incremental]]\n\n"
333 "Dump the contents of filesystem to stdout in a 'dumpfile'\n"
334 "portable format, sending feedback to stderr. Dump revisions\n"
335 "LOWER rev through UPPER rev. If no revisions are given, dump all\n"
336 "revision trees. If only LOWER is given, dump that one revision tree.\n"
337 "If --incremental is passed, the first revision dumped will describe\n"
338 "only the paths changed in that revision; otherwise it will describe\n"
339 "every path present in the repository as of that revision. (In either\n"
340 "case, the second and subsequent revisions, if any, describe only paths\n"
341 "changed in those revisions.)\n"),
342 {'r', svnadmin__incremental, svnadmin__deltas, 'q', 'M'} },
344 {"freeze", subcommand_freeze, {0}, N_
345 ("usage: 1. svnadmin freeze REPOS_PATH PROGRAM [ARG...]\n"
346 " 2. svnadmin freeze -F FILE PROGRAM [ARG...]\n\n"
347 "1. Run PROGRAM passing ARGS while holding a write-lock on REPOS_PATH.\n"
349 "2. Like 1 except all repositories listed in FILE are locked. The file\n"
350 " format is repository paths separated by newlines. Repositories are\n"
351 " locked in the same order as they are listed in the file.\n"),
354 {"help", subcommand_help, {"?", "h"}, N_
355 ("usage: svnadmin help [SUBCOMMAND...]\n\n"
356 "Describe the usage of this program or its subcommands.\n"),
359 {"hotcopy", subcommand_hotcopy, {0}, N_
360 ("usage: svnadmin hotcopy REPOS_PATH NEW_REPOS_PATH\n\n"
361 "Make a hot copy of a repository.\n"
362 "If --incremental is passed, data which already exists at the destination\n"
363 "is not copied again. Incremental mode is implemented for FSFS repositories.\n"),
364 {svnadmin__clean_logs, svnadmin__incremental} },
366 {"list-dblogs", subcommand_list_dblogs, {0}, N_
367 ("usage: svnadmin list-dblogs REPOS_PATH\n\n"
368 "List all Berkeley DB log files.\n\n"
369 "WARNING: Modifying or deleting logfiles which are still in use\n"
370 "will cause your repository to be corrupted.\n"),
373 {"list-unused-dblogs", subcommand_list_unused_dblogs, {0}, N_
374 ("usage: svnadmin list-unused-dblogs REPOS_PATH\n\n"
375 "List unused Berkeley DB log files.\n\n"),
378 {"load", subcommand_load, {0}, N_
379 ("usage: svnadmin load REPOS_PATH\n\n"
380 "Read a 'dumpfile'-formatted stream from stdin, committing\n"
381 "new revisions into the repository's filesystem. If the repository\n"
382 "was previously empty, its UUID will, by default, be changed to the\n"
383 "one specified in the stream. Progress feedback is sent to stdout.\n"
384 "If --revision is specified, limit the loaded revisions to only those\n"
385 "in the dump stream whose revision numbers match the specified range.\n"),
386 {'q', 'r', svnadmin__ignore_uuid, svnadmin__force_uuid,
387 svnadmin__use_pre_commit_hook, svnadmin__use_post_commit_hook,
388 svnadmin__parent_dir, svnadmin__bypass_prop_validation, 'M'} },
390 {"lock", subcommand_lock, {0}, N_
391 ("usage: svnadmin lock REPOS_PATH PATH USERNAME COMMENT-FILE [TOKEN]\n\n"
392 "Lock PATH by USERNAME setting comments from COMMENT-FILE.\n"
393 "If provided, use TOKEN as lock token. Use --bypass-hooks to avoid\n"
394 "triggering the pre-lock and post-lock hook scripts.\n"),
395 {svnadmin__bypass_hooks} },
397 {"lslocks", subcommand_lslocks, {0}, N_
398 ("usage: svnadmin lslocks REPOS_PATH [PATH-IN-REPOS]\n\n"
399 "Print descriptions of all locks on or under PATH-IN-REPOS (which,\n"
400 "if not provided, is the root of the repository).\n"),
403 {"lstxns", subcommand_lstxns, {0}, N_
404 ("usage: svnadmin lstxns REPOS_PATH\n\n"
405 "Print the names of all uncommitted transactions.\n"),
408 {"pack", subcommand_pack, {0}, N_
409 ("usage: svnadmin pack REPOS_PATH\n\n"
410 "Possibly compact the repository into a more efficient storage model.\n"
411 "This may not apply to all repositories, in which case, exit.\n"),
414 {"recover", subcommand_recover, {0}, N_
415 ("usage: svnadmin recover REPOS_PATH\n\n"
416 "Run the recovery procedure on a repository. Do this if you've\n"
417 "been getting errors indicating that recovery ought to be run.\n"
418 "Berkeley DB recovery requires exclusive access and will\n"
419 "exit if the repository is in use by another process.\n"),
422 {"rmlocks", subcommand_rmlocks, {0}, N_
423 ("usage: svnadmin rmlocks REPOS_PATH LOCKED_PATH...\n\n"
424 "Unconditionally remove lock from each LOCKED_PATH.\n"),
427 {"rmtxns", subcommand_rmtxns, {0}, N_
428 ("usage: svnadmin rmtxns REPOS_PATH TXN_NAME...\n\n"
429 "Delete the named transaction(s).\n"),
432 {"setlog", subcommand_setlog, {0}, N_
433 ("usage: svnadmin setlog REPOS_PATH -r REVISION FILE\n\n"
434 "Set the log-message on revision REVISION to the contents of FILE. Use\n"
435 "--bypass-hooks to avoid triggering the revision-property-related hooks\n"
436 "(for example, if you do not want an email notification sent\n"
437 "from your post-revprop-change hook, or because the modification of\n"
438 "revision properties has not been enabled in the pre-revprop-change\n"
440 "NOTE: Revision properties are not versioned, so this command will\n"
441 "overwrite the previous log message.\n"),
442 {'r', svnadmin__bypass_hooks} },
444 {"setrevprop", subcommand_setrevprop, {0}, N_
445 ("usage: svnadmin setrevprop REPOS_PATH -r REVISION NAME FILE\n\n"
446 "Set the property NAME on revision REVISION to the contents of FILE. Use\n"
447 "--use-pre-revprop-change-hook/--use-post-revprop-change-hook to trigger\n"
448 "the revision property-related hooks (for example, if you want an email\n"
449 "notification sent from your post-revprop-change hook).\n\n"
450 "NOTE: Revision properties are not versioned, so this command will\n"
451 "overwrite the previous value of the property.\n"),
452 {'r', svnadmin__use_pre_revprop_change_hook,
453 svnadmin__use_post_revprop_change_hook} },
455 {"setuuid", subcommand_setuuid, {0}, N_
456 ("usage: svnadmin setuuid REPOS_PATH [NEW_UUID]\n\n"
457 "Reset the repository UUID for the repository located at REPOS_PATH. If\n"
458 "NEW_UUID is provided, use that as the new repository UUID; otherwise,\n"
459 "generate a brand new UUID for the repository.\n"),
462 {"unlock", subcommand_unlock, {0}, N_
463 ("usage: svnadmin unlock REPOS_PATH LOCKED_PATH USERNAME TOKEN\n\n"
464 "Unlock LOCKED_PATH (as USERNAME) after verifying that the token\n"
465 "associated with the lock matches TOKEN. Use --bypass-hooks to avoid\n"
466 "triggering the pre-unlock and post-unlock hook scripts.\n"),
467 {svnadmin__bypass_hooks} },
469 {"upgrade", subcommand_upgrade, {0}, N_
470 ("usage: svnadmin upgrade REPOS_PATH\n\n"
471 "Upgrade the repository located at REPOS_PATH to the latest supported\n"
472 "schema version.\n\n"
473 "This functionality is provided as a convenience for repository\n"
474 "administrators who wish to make use of new Subversion functionality\n"
475 "without having to undertake a potentially costly full repository dump\n"
476 "and load operation. As such, the upgrade performs only the minimum\n"
477 "amount of work needed to accomplish this while still maintaining the\n"
478 "integrity of the repository. It does not guarantee the most optimized\n"
479 "repository state as a dump and subsequent load would.\n"),
482 {"verify", subcommand_verify, {0}, N_
483 ("usage: svnadmin verify REPOS_PATH\n\n"
484 "Verify the data stored in the repository.\n"),
485 {'t', 'r', 'q', 'M'} },
487 { NULL, NULL, {0}, NULL, {0} }
491 /* Baton for passing option/argument state to a subcommand function. */
492 struct svnadmin_opt_state
494 const char *repository_path;
495 const char *fs_type; /* --fs-type */
496 svn_boolean_t pre_1_4_compatible; /* --pre-1.4-compatible */
497 svn_boolean_t pre_1_5_compatible; /* --pre-1.5-compatible */
498 svn_boolean_t pre_1_6_compatible; /* --pre-1.6-compatible */
499 svn_version_t *compatible_version; /* --compatible-version */
500 svn_opt_revision_t start_revision, end_revision; /* -r X[:Y] */
501 const char *txn_id; /* -t TXN */
502 svn_boolean_t help; /* --help or -? */
503 svn_boolean_t version; /* --version */
504 svn_boolean_t incremental; /* --incremental */
505 svn_boolean_t use_deltas; /* --deltas */
506 svn_boolean_t use_pre_commit_hook; /* --use-pre-commit-hook */
507 svn_boolean_t use_post_commit_hook; /* --use-post-commit-hook */
508 svn_boolean_t use_pre_revprop_change_hook; /* --use-pre-revprop-change-hook */
509 svn_boolean_t use_post_revprop_change_hook; /* --use-post-revprop-change-hook */
510 svn_boolean_t quiet; /* --quiet */
511 svn_boolean_t bdb_txn_nosync; /* --bdb-txn-nosync */
512 svn_boolean_t bdb_log_keep; /* --bdb-log-keep */
513 svn_boolean_t clean_logs; /* --clean-logs */
514 svn_boolean_t bypass_hooks; /* --bypass-hooks */
515 svn_boolean_t wait; /* --wait */
516 svn_boolean_t bypass_prop_validation; /* --bypass-prop-validation */
517 enum svn_repos_load_uuid uuid_action; /* --ignore-uuid,
519 apr_uint64_t memory_cache_size; /* --memory-cache-size M */
520 const char *parent_dir;
521 svn_stringbuf_t *filedata; /* --file */
523 const char *config_dir; /* Overriding Configuration Directory */
527 /* Set *REVNUM to the revision specified by REVISION (or to
528 SVN_INVALID_REVNUM if that has the type 'unspecified'),
529 possibly making use of the YOUNGEST revision number in REPOS. */
531 get_revnum(svn_revnum_t *revnum, const svn_opt_revision_t *revision,
532 svn_revnum_t youngest, svn_repos_t *repos, apr_pool_t *pool)
534 if (revision->kind == svn_opt_revision_number)
535 *revnum = revision->value.number;
536 else if (revision->kind == svn_opt_revision_head)
538 else if (revision->kind == svn_opt_revision_date)
539 SVN_ERR(svn_repos_dated_revision(revnum, repos, revision->value.date,
541 else if (revision->kind == svn_opt_revision_unspecified)
542 *revnum = SVN_INVALID_REVNUM;
544 return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
545 _("Invalid revision specifier"));
547 if (*revnum > youngest)
548 return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
549 _("Revisions must not be greater than the youngest revision (%ld)"),
555 /* Set *PATH to an internal-style, UTF8-encoded, local dirent path
556 allocated from POOL and parsed from raw command-line argument ARG. */
558 target_arg_to_dirent(const char **dirent,
564 SVN_ERR(svn_utf_cstring_to_utf8(&path, arg, pool));
565 if (svn_path_is_url(path))
566 return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
567 "Path '%s' is not a local path", path);
568 *dirent = svn_dirent_internal_style(path, pool);
572 /* Parse the remaining command-line arguments from OS, returning them
573 in a new array *ARGS (allocated from POOL) and optionally verifying
574 that we got the expected number thereof. If MIN_EXPECTED is not
575 negative, return an error if the function would return fewer than
576 MIN_EXPECTED arguments. If MAX_EXPECTED is not negative, return an
577 error if the function would return more than MAX_EXPECTED
580 As a special case, when MIN_EXPECTED and MAX_EXPECTED are both 0,
581 allow ARGS to be NULL. */
583 parse_args(apr_array_header_t **args,
589 int num_args = os ? (os->argc - os->ind) : 0;
591 if (min_expected || max_expected)
592 SVN_ERR_ASSERT(args);
594 if ((min_expected >= 0) && (num_args < min_expected))
595 return svn_error_create(SVN_ERR_CL_INSUFFICIENT_ARGS, 0,
596 "Not enough arguments");
597 if ((max_expected >= 0) && (num_args > max_expected))
598 return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, 0,
599 "Too many arguments");
602 *args = apr_array_make(pool, num_args, sizeof(const char *));
605 while (os->ind < os->argc)
606 APR_ARRAY_PUSH(*args, const char *) =
607 apr_pstrdup(pool, os->argv[os->ind++]);
614 /* This implements `svn_opt_subcommand_t'. */
616 subcommand_crashtest(apr_getopt_t *os, void *baton, apr_pool_t *pool)
618 struct svnadmin_opt_state *opt_state = baton;
621 SVN_ERR(open_repos(&repos, opt_state->repository_path, pool));
622 SVN_ERR_MALFUNCTION();
624 /* merely silence a compiler warning (this will never be executed) */
628 /* This implements `svn_opt_subcommand_t'. */
630 subcommand_create(apr_getopt_t *os, void *baton, apr_pool_t *pool)
632 struct svnadmin_opt_state *opt_state = baton;
634 apr_hash_t *fs_config = apr_hash_make(pool);
636 /* Expect no more arguments. */
637 SVN_ERR(parse_args(NULL, os, 0, 0, pool));
639 svn_hash_sets(fs_config, SVN_FS_CONFIG_BDB_TXN_NOSYNC,
640 (opt_state->bdb_txn_nosync ? "1" :"0"));
642 svn_hash_sets(fs_config, SVN_FS_CONFIG_BDB_LOG_AUTOREMOVE,
643 (opt_state->bdb_log_keep ? "0" :"1"));
645 if (opt_state->fs_type)
647 /* With 1.8 we are announcing that BDB is deprecated. No support
648 * has been removed and it will continue to work until some future
649 * date. The purpose here is to discourage people from creating
650 * new BDB repositories which they will need to dump/load into
651 * FSFS or some new FS type in the future. */
652 if (0 == strcmp(opt_state->fs_type, SVN_FS_TYPE_BDB))
654 SVN_ERR(svn_cmdline_fprintf(
657 " The \"%s\" repository back-end is deprecated,"
658 " consider using \"%s\" instead.\n"),
659 "svnadmin: ", SVN_FS_TYPE_BDB, SVN_FS_TYPE_FSFS));
662 svn_hash_sets(fs_config, SVN_FS_CONFIG_FS_TYPE, opt_state->fs_type);
665 /* Prior to 1.8, we had explicit options to specify compatibility
666 with a handful of prior Subversion releases. */
667 if (opt_state->pre_1_4_compatible)
668 svn_hash_sets(fs_config, SVN_FS_CONFIG_PRE_1_4_COMPATIBLE, "1");
669 if (opt_state->pre_1_5_compatible)
670 svn_hash_sets(fs_config, SVN_FS_CONFIG_PRE_1_5_COMPATIBLE, "1");
671 if (opt_state->pre_1_6_compatible)
672 svn_hash_sets(fs_config, SVN_FS_CONFIG_PRE_1_6_COMPATIBLE, "1");
674 /* In 1.8, we figured out that we didn't have to keep extending this
675 madness indefinitely. */
676 if (opt_state->compatible_version)
678 if (! svn_version__at_least(opt_state->compatible_version, 1, 4, 0))
679 svn_hash_sets(fs_config, SVN_FS_CONFIG_PRE_1_4_COMPATIBLE, "1");
680 if (! svn_version__at_least(opt_state->compatible_version, 1, 5, 0))
681 svn_hash_sets(fs_config, SVN_FS_CONFIG_PRE_1_5_COMPATIBLE, "1");
682 if (! svn_version__at_least(opt_state->compatible_version, 1, 6, 0))
683 svn_hash_sets(fs_config, SVN_FS_CONFIG_PRE_1_6_COMPATIBLE, "1");
684 if (! svn_version__at_least(opt_state->compatible_version, 1, 8, 0))
685 svn_hash_sets(fs_config, SVN_FS_CONFIG_PRE_1_8_COMPATIBLE, "1");
688 if (opt_state->compatible_version
689 && ! svn_version__at_least(opt_state->compatible_version, 1, 1, 0)
690 /* ### TODO: this NULL check hard-codes knowledge of the library's
691 default fs-type value */
692 && (opt_state->fs_type == NULL
693 || !strcmp(opt_state->fs_type, SVN_FS_TYPE_FSFS)))
695 return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
696 _("Repositories compatible with 1.0.x must use "
700 SVN_ERR(svn_repos_create(&repos, opt_state->repository_path,
701 NULL, NULL, NULL, fs_config, pool));
702 svn_fs_set_warning_func(svn_repos_fs(repos), warning_func, NULL);
707 /* This implements `svn_opt_subcommand_t'. */
709 subcommand_deltify(apr_getopt_t *os, void *baton, apr_pool_t *pool)
711 struct svnadmin_opt_state *opt_state = baton;
714 svn_revnum_t start = SVN_INVALID_REVNUM, end = SVN_INVALID_REVNUM;
715 svn_revnum_t youngest, revision;
716 apr_pool_t *subpool = svn_pool_create(pool);
718 /* Expect no more arguments. */
719 SVN_ERR(parse_args(NULL, os, 0, 0, pool));
721 SVN_ERR(open_repos(&repos, opt_state->repository_path, pool));
722 fs = svn_repos_fs(repos);
723 SVN_ERR(svn_fs_youngest_rev(&youngest, fs, pool));
725 /* Find the revision numbers at which to start and end. */
726 SVN_ERR(get_revnum(&start, &opt_state->start_revision,
727 youngest, repos, pool));
728 SVN_ERR(get_revnum(&end, &opt_state->end_revision,
729 youngest, repos, pool));
731 /* Fill in implied revisions if necessary. */
732 if (start == SVN_INVALID_REVNUM)
734 if (end == SVN_INVALID_REVNUM)
738 return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
739 _("First revision cannot be higher than second"));
741 /* Loop over the requested revision range, performing the
742 predecessor deltification on paths changed in each. */
743 for (revision = start; revision <= end; revision++)
745 svn_pool_clear(subpool);
746 SVN_ERR(check_cancel(NULL));
747 if (! opt_state->quiet)
748 SVN_ERR(svn_cmdline_printf(subpool, _("Deltifying revision %ld..."),
750 SVN_ERR(svn_fs_deltify_revision(fs, revision, subpool));
751 if (! opt_state->quiet)
752 SVN_ERR(svn_cmdline_printf(subpool, _("done.\n")));
754 svn_pool_destroy(subpool);
760 /* Implementation of svn_repos_notify_func_t to wrap the output to a
761 response stream for svn_repos_dump_fs2() and svn_repos_verify_fs() */
763 repos_notify_handler(void *baton,
764 const svn_repos_notify_t *notify,
765 apr_pool_t *scratch_pool)
767 svn_stream_t *feedback_stream = baton;
770 switch (notify->action)
772 case svn_repos_notify_warning:
773 svn_error_clear(svn_stream_printf(feedback_stream, scratch_pool,
774 "WARNING 0x%04x: %s\n", notify->warning,
775 notify->warning_str));
778 case svn_repos_notify_dump_rev_end:
779 svn_error_clear(svn_stream_printf(feedback_stream, scratch_pool,
780 _("* Dumped revision %ld.\n"),
784 case svn_repos_notify_verify_rev_end:
785 svn_error_clear(svn_stream_printf(feedback_stream, scratch_pool,
786 _("* Verified revision %ld.\n"),
790 case svn_repos_notify_verify_rev_structure:
791 if (notify->revision == SVN_INVALID_REVNUM)
792 svn_error_clear(svn_stream_printf(feedback_stream, scratch_pool,
793 _("* Verifying repository metadata ...\n")));
795 svn_error_clear(svn_stream_printf(feedback_stream, scratch_pool,
796 _("* Verifying metadata at revision %ld ...\n"),
800 case svn_repos_notify_pack_shard_start:
802 const char *shardstr = apr_psprintf(scratch_pool,
805 svn_error_clear(svn_stream_printf(feedback_stream, scratch_pool,
806 _("Packing revisions in shard %s..."),
811 case svn_repos_notify_pack_shard_end:
812 svn_error_clear(svn_stream_puts(feedback_stream, _("done.\n")));
815 case svn_repos_notify_pack_shard_start_revprop:
817 const char *shardstr = apr_psprintf(scratch_pool,
820 svn_error_clear(svn_stream_printf(feedback_stream, scratch_pool,
821 _("Packing revprops in shard %s..."),
826 case svn_repos_notify_pack_shard_end_revprop:
827 svn_error_clear(svn_stream_puts(feedback_stream, _("done.\n")));
830 case svn_repos_notify_load_txn_committed:
831 if (notify->old_revision == SVN_INVALID_REVNUM)
833 svn_error_clear(svn_stream_printf(feedback_stream, scratch_pool,
834 _("\n------- Committed revision %ld >>>\n\n"),
835 notify->new_revision));
839 svn_error_clear(svn_stream_printf(feedback_stream, scratch_pool,
840 _("\n------- Committed new rev %ld"
841 " (loaded from original rev %ld"
842 ") >>>\n\n"), notify->new_revision,
843 notify->old_revision));
847 case svn_repos_notify_load_node_start:
849 switch (notify->node_action)
851 case svn_node_action_change:
852 svn_error_clear(svn_stream_printf(feedback_stream, scratch_pool,
853 _(" * editing path : %s ..."),
857 case svn_node_action_delete:
858 svn_error_clear(svn_stream_printf(feedback_stream, scratch_pool,
859 _(" * deleting path : %s ..."),
863 case svn_node_action_add:
864 svn_error_clear(svn_stream_printf(feedback_stream, scratch_pool,
865 _(" * adding path : %s ..."),
869 case svn_node_action_replace:
870 svn_error_clear(svn_stream_printf(feedback_stream, scratch_pool,
871 _(" * replacing path : %s ..."),
879 case svn_repos_notify_load_node_done:
880 svn_error_clear(svn_stream_printf(feedback_stream, scratch_pool,
881 "%s", _(" done.\n")));
884 case svn_repos_notify_load_copied_node:
886 svn_error_clear(svn_stream_write(feedback_stream, "COPIED...", &len));
889 case svn_repos_notify_load_txn_start:
890 svn_error_clear(svn_stream_printf(feedback_stream, scratch_pool,
891 _("<<< Started new transaction, based on "
892 "original revision %ld\n"),
893 notify->old_revision));
896 case svn_repos_notify_load_skipped_rev:
897 svn_error_clear(svn_stream_printf(feedback_stream, scratch_pool,
898 _("<<< Skipped original revision %ld\n"),
899 notify->old_revision));
902 case svn_repos_notify_load_normalized_mergeinfo:
903 svn_error_clear(svn_stream_printf(feedback_stream, scratch_pool,
904 _(" removing '\\r' from %s ..."),
905 SVN_PROP_MERGEINFO));
908 case svn_repos_notify_mutex_acquired:
909 /* Enable cancellation signal handlers. */
910 setup_cancellation_signals(signal_handler);
913 case svn_repos_notify_recover_start:
914 svn_error_clear(svn_stream_printf(feedback_stream, scratch_pool,
915 _("Repository lock acquired.\n"
916 "Please wait; recovering the"
917 " repository may take some time...\n")));
920 case svn_repos_notify_upgrade_start:
921 svn_error_clear(svn_stream_puts(feedback_stream,
922 _("Repository lock acquired.\n"
923 "Please wait; upgrading the"
924 " repository may take some time...\n")));
933 /* Baton for recode_write(). */
934 struct recode_write_baton
940 /* This implements the 'svn_write_fn_t' interface.
942 Write DATA to ((struct recode_write_baton *) BATON)->out, in the
943 console encoding, using svn_cmdline_fprintf(). DATA is a
944 UTF8-encoded C string, therefore ignore LEN.
946 ### This recoding mechanism might want to be abstracted into
947 ### svn_io.h or svn_cmdline.h, if it proves useful elsewhere. */
948 static svn_error_t *recode_write(void *baton,
952 struct recode_write_baton *rwb = baton;
953 svn_pool_clear(rwb->pool);
954 return svn_cmdline_fputs(data, rwb->out, rwb->pool);
957 /* Create a stream, to write to STD_STREAM, that uses recode_write()
958 to perform UTF-8 to console encoding translation. */
959 static svn_stream_t *
960 recode_stream_create(FILE *std_stream, apr_pool_t *pool)
962 struct recode_write_baton *std_stream_rwb =
963 apr_palloc(pool, sizeof(struct recode_write_baton));
965 svn_stream_t *rw_stream = svn_stream_create(std_stream_rwb, pool);
966 std_stream_rwb->pool = svn_pool_create(pool);
967 std_stream_rwb->out = std_stream;
968 svn_stream_set_write(rw_stream, recode_write);
973 /* This implements `svn_opt_subcommand_t'. */
975 subcommand_dump(apr_getopt_t *os, void *baton, apr_pool_t *pool)
977 struct svnadmin_opt_state *opt_state = baton;
980 svn_stream_t *stdout_stream;
981 svn_revnum_t lower = SVN_INVALID_REVNUM, upper = SVN_INVALID_REVNUM;
982 svn_revnum_t youngest;
983 svn_stream_t *progress_stream = NULL;
985 /* Expect no more arguments. */
986 SVN_ERR(parse_args(NULL, os, 0, 0, pool));
988 SVN_ERR(open_repos(&repos, opt_state->repository_path, pool));
989 fs = svn_repos_fs(repos);
990 SVN_ERR(svn_fs_youngest_rev(&youngest, fs, pool));
992 /* Find the revision numbers at which to start and end. */
993 SVN_ERR(get_revnum(&lower, &opt_state->start_revision,
994 youngest, repos, pool));
995 SVN_ERR(get_revnum(&upper, &opt_state->end_revision,
996 youngest, repos, pool));
998 /* Fill in implied revisions if necessary. */
999 if (lower == SVN_INVALID_REVNUM)
1004 else if (upper == SVN_INVALID_REVNUM)
1010 return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
1011 _("First revision cannot be higher than second"));
1013 SVN_ERR(svn_stream_for_stdout(&stdout_stream, pool));
1015 /* Progress feedback goes to STDERR, unless they asked to suppress it. */
1016 if (! opt_state->quiet)
1017 progress_stream = recode_stream_create(stderr, pool);
1019 SVN_ERR(svn_repos_dump_fs3(repos, stdout_stream, lower, upper,
1020 opt_state->incremental, opt_state->use_deltas,
1021 !opt_state->quiet ? repos_notify_handler : NULL,
1022 progress_stream, check_cancel, NULL, pool));
1024 return SVN_NO_ERROR;
1027 struct freeze_baton_t {
1028 const char *command;
1033 /* Implements svn_repos_freeze_func_t */
1034 static svn_error_t *
1035 freeze_body(void *baton,
1038 struct freeze_baton_t *b = baton;
1039 apr_status_t apr_err;
1040 apr_file_t *infile, *outfile, *errfile;
1042 apr_err = apr_file_open_stdin(&infile, pool);
1044 return svn_error_wrap_apr(apr_err, "Can't open stdin");
1045 apr_err = apr_file_open_stdout(&outfile, pool);
1047 return svn_error_wrap_apr(apr_err, "Can't open stdout");
1048 apr_err = apr_file_open_stderr(&errfile, pool);
1050 return svn_error_wrap_apr(apr_err, "Can't open stderr");
1052 SVN_ERR(svn_io_run_cmd(NULL, b->command, b->args, &b->status,
1054 infile, outfile, errfile, pool));
1056 return SVN_NO_ERROR;
1059 static svn_error_t *
1060 subcommand_freeze(apr_getopt_t *os, void *baton, apr_pool_t *pool)
1062 struct svnadmin_opt_state *opt_state = baton;
1063 apr_array_header_t *paths;
1064 apr_array_header_t *args;
1066 struct freeze_baton_t b;
1068 SVN_ERR(svn_opt_parse_all_args(&args, os, pool));
1071 return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, 0,
1072 _("No program provided"));
1074 if (!opt_state->filedata)
1076 /* One repository on the command line. */
1077 paths = apr_array_make(pool, 1, sizeof(const char *));
1078 APR_ARRAY_PUSH(paths, const char *) = opt_state->repository_path;
1082 /* All repositories in filedata. */
1083 paths = svn_cstring_split(opt_state->filedata->data, "\n", FALSE, pool);
1086 b.command = APR_ARRAY_IDX(args, 0, const char *);
1087 b.args = apr_palloc(pool, sizeof(char *) * (args->nelts + 1));
1088 for (i = 0; i < args->nelts; ++i)
1089 b.args[i] = APR_ARRAY_IDX(args, i, const char *);
1090 b.args[args->nelts] = NULL;
1092 SVN_ERR(svn_repos_freeze(paths, freeze_body, &b, pool));
1094 /* Make any non-zero status visible to the user. */
1098 return SVN_NO_ERROR;
1102 /* This implements `svn_opt_subcommand_t'. */
1103 static svn_error_t *
1104 subcommand_help(apr_getopt_t *os, void *baton, apr_pool_t *pool)
1106 struct svnadmin_opt_state *opt_state = baton;
1107 const char *header =
1108 _("general usage: svnadmin SUBCOMMAND REPOS_PATH [ARGS & OPTIONS ...]\n"
1109 "Type 'svnadmin help <subcommand>' for help on a specific subcommand.\n"
1110 "Type 'svnadmin --version' to see the program version and FS modules.\n"
1112 "Available subcommands:\n");
1114 const char *fs_desc_start
1115 = _("The following repository back-end (FS) modules are available:\n\n");
1117 svn_stringbuf_t *version_footer;
1119 version_footer = svn_stringbuf_create(fs_desc_start, pool);
1120 SVN_ERR(svn_fs_print_modules(version_footer, pool));
1122 SVN_ERR(svn_opt_print_help4(os, "svnadmin",
1123 opt_state ? opt_state->version : FALSE,
1124 opt_state ? opt_state->quiet : FALSE,
1125 /*###opt_state ? opt_state->verbose :*/ FALSE,
1126 version_footer->data,
1127 header, cmd_table, options_table, NULL, NULL,
1130 return SVN_NO_ERROR;
1134 /* Set *REVNUM to the revision number of a numeric REV, or to
1135 SVN_INVALID_REVNUM if REV is unspecified. */
1136 static svn_error_t *
1137 optrev_to_revnum(svn_revnum_t *revnum, const svn_opt_revision_t *opt_rev)
1139 if (opt_rev->kind == svn_opt_revision_number)
1141 *revnum = opt_rev->value.number;
1142 if (! SVN_IS_VALID_REVNUM(*revnum))
1143 return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
1144 _("Invalid revision number (%ld) specified"),
1147 else if (opt_rev->kind == svn_opt_revision_unspecified)
1149 *revnum = SVN_INVALID_REVNUM;
1153 return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
1154 _("Non-numeric revision specified"));
1156 return SVN_NO_ERROR;
1160 /* This implements `svn_opt_subcommand_t'. */
1161 static svn_error_t *
1162 subcommand_load(apr_getopt_t *os, void *baton, apr_pool_t *pool)
1165 struct svnadmin_opt_state *opt_state = baton;
1167 svn_revnum_t lower = SVN_INVALID_REVNUM, upper = SVN_INVALID_REVNUM;
1168 svn_stream_t *stdin_stream, *stdout_stream = NULL;
1170 /* Expect no more arguments. */
1171 SVN_ERR(parse_args(NULL, os, 0, 0, pool));
1173 /* Find the revision numbers at which to start and end. We only
1174 support a limited set of revision kinds: number and unspecified. */
1175 SVN_ERR(optrev_to_revnum(&lower, &opt_state->start_revision));
1176 SVN_ERR(optrev_to_revnum(&upper, &opt_state->end_revision));
1178 /* Fill in implied revisions if necessary. */
1179 if ((upper == SVN_INVALID_REVNUM) && (lower != SVN_INVALID_REVNUM))
1183 else if ((upper != SVN_INVALID_REVNUM) && (lower == SVN_INVALID_REVNUM))
1188 /* Ensure correct range ordering. */
1191 return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
1192 _("First revision cannot be higher than second"));
1195 SVN_ERR(open_repos(&repos, opt_state->repository_path, pool));
1197 /* Read the stream from STDIN. Users can redirect a file. */
1198 SVN_ERR(svn_stream_for_stdin(&stdin_stream, pool));
1200 /* Progress feedback goes to STDOUT, unless they asked to suppress it. */
1201 if (! opt_state->quiet)
1202 stdout_stream = recode_stream_create(stdout, pool);
1204 err = svn_repos_load_fs4(repos, stdin_stream, lower, upper,
1205 opt_state->uuid_action, opt_state->parent_dir,
1206 opt_state->use_pre_commit_hook,
1207 opt_state->use_post_commit_hook,
1208 !opt_state->bypass_prop_validation,
1209 opt_state->quiet ? NULL : repos_notify_handler,
1210 stdout_stream, check_cancel, NULL, pool);
1211 if (err && err->apr_err == SVN_ERR_BAD_PROPERTY_VALUE)
1212 return svn_error_quick_wrap(err,
1213 _("Invalid property value found in "
1214 "dumpstream; consider repairing the source "
1215 "or using --bypass-prop-validation while "
1221 /* This implements `svn_opt_subcommand_t'. */
1222 static svn_error_t *
1223 subcommand_lstxns(apr_getopt_t *os, void *baton, apr_pool_t *pool)
1225 struct svnadmin_opt_state *opt_state = baton;
1228 apr_array_header_t *txns;
1231 /* Expect no more arguments. */
1232 SVN_ERR(parse_args(NULL, os, 0, 0, pool));
1234 SVN_ERR(open_repos(&repos, opt_state->repository_path, pool));
1235 fs = svn_repos_fs(repos);
1236 SVN_ERR(svn_fs_list_transactions(&txns, fs, pool));
1238 /* Loop, printing revisions. */
1239 for (i = 0; i < txns->nelts; i++)
1241 SVN_ERR(svn_cmdline_printf(pool, "%s\n",
1242 APR_ARRAY_IDX(txns, i, const char *)));
1245 return SVN_NO_ERROR;
1249 /* This implements `svn_opt_subcommand_t'. */
1250 static svn_error_t *
1251 subcommand_recover(apr_getopt_t *os, void *baton, apr_pool_t *pool)
1253 svn_revnum_t youngest_rev;
1256 struct svnadmin_opt_state *opt_state = baton;
1257 svn_stream_t *stdout_stream;
1259 /* Expect no more arguments. */
1260 SVN_ERR(parse_args(NULL, os, 0, 0, pool));
1262 SVN_ERR(svn_stream_for_stdout(&stdout_stream, pool));
1264 /* Restore default signal handlers until after we have acquired the
1265 * exclusive lock so that the user interrupt before we actually
1266 * touch the repository. */
1267 setup_cancellation_signals(SIG_DFL);
1269 err = svn_repos_recover4(opt_state->repository_path, TRUE,
1270 repos_notify_handler, stdout_stream,
1271 check_cancel, NULL, pool);
1274 if (! APR_STATUS_IS_EAGAIN(err->apr_err))
1276 svn_error_clear(err);
1277 if (! opt_state->wait)
1278 return svn_error_create(SVN_ERR_REPOS_LOCKED, NULL,
1279 _("Failed to get exclusive repository "
1280 "access; perhaps another process\n"
1281 "such as httpd, svnserve or svn "
1283 SVN_ERR(svn_cmdline_printf(pool,
1284 _("Waiting on repository lock; perhaps"
1285 " another process has it open?\n")));
1286 SVN_ERR(svn_cmdline_fflush(stdout));
1287 SVN_ERR(svn_repos_recover4(opt_state->repository_path, FALSE,
1288 repos_notify_handler, stdout_stream,
1289 check_cancel, NULL, pool));
1292 SVN_ERR(svn_cmdline_printf(pool, _("\nRecovery completed.\n")));
1294 /* Since db transactions may have been replayed, it's nice to tell
1295 people what the latest revision is. It also proves that the
1296 recovery actually worked. */
1297 SVN_ERR(open_repos(&repos, opt_state->repository_path, pool));
1298 SVN_ERR(svn_fs_youngest_rev(&youngest_rev, svn_repos_fs(repos), pool));
1299 SVN_ERR(svn_cmdline_printf(pool, _("The latest repos revision is %ld.\n"),
1302 return SVN_NO_ERROR;
1306 /* This implements `svn_opt_subcommand_t'. */
1307 static svn_error_t *
1308 list_dblogs(apr_getopt_t *os, void *baton, svn_boolean_t only_unused,
1311 struct svnadmin_opt_state *opt_state = baton;
1312 apr_array_header_t *logfiles;
1315 /* Expect no more arguments. */
1316 SVN_ERR(parse_args(NULL, os, 0, 0, pool));
1318 SVN_ERR(svn_repos_db_logfiles(&logfiles,
1319 opt_state->repository_path,
1323 /* Loop, printing log files. We append the log paths to the
1324 repository path, making sure to return everything to the native
1325 style before printing. */
1326 for (i = 0; i < logfiles->nelts; i++)
1328 const char *log_utf8;
1329 log_utf8 = svn_dirent_join(opt_state->repository_path,
1330 APR_ARRAY_IDX(logfiles, i, const char *),
1332 log_utf8 = svn_dirent_local_style(log_utf8, pool);
1333 SVN_ERR(svn_cmdline_printf(pool, "%s\n", log_utf8));
1336 return SVN_NO_ERROR;
1340 /* This implements `svn_opt_subcommand_t'. */
1341 static svn_error_t *
1342 subcommand_list_dblogs(apr_getopt_t *os, void *baton, apr_pool_t *pool)
1344 SVN_ERR(list_dblogs(os, baton, FALSE, pool));
1345 return SVN_NO_ERROR;
1349 /* This implements `svn_opt_subcommand_t'. */
1350 static svn_error_t *
1351 subcommand_list_unused_dblogs(apr_getopt_t *os, void *baton, apr_pool_t *pool)
1353 /* Expect no more arguments. */
1354 SVN_ERR(parse_args(NULL, os, 0, 0, pool));
1356 SVN_ERR(list_dblogs(os, baton, TRUE, pool));
1357 return SVN_NO_ERROR;
1361 /* This implements `svn_opt_subcommand_t'. */
1362 static svn_error_t *
1363 subcommand_rmtxns(apr_getopt_t *os, void *baton, apr_pool_t *pool)
1365 struct svnadmin_opt_state *opt_state = baton;
1369 apr_array_header_t *args;
1371 apr_pool_t *subpool = svn_pool_create(pool);
1373 SVN_ERR(svn_opt_parse_all_args(&args, os, pool));
1375 SVN_ERR(open_repos(&repos, opt_state->repository_path, pool));
1376 fs = svn_repos_fs(repos);
1378 /* All the rest of the arguments are transaction names. */
1379 for (i = 0; i < args->nelts; i++)
1381 const char *txn_name = APR_ARRAY_IDX(args, i, const char *);
1382 const char *txn_name_utf8;
1385 svn_pool_clear(subpool);
1387 SVN_ERR(svn_utf_cstring_to_utf8(&txn_name_utf8, txn_name, subpool));
1389 /* Try to open the txn. If that succeeds, try to abort it. */
1390 err = svn_fs_open_txn(&txn, fs, txn_name_utf8, subpool);
1392 err = svn_fs_abort_txn(txn, subpool);
1394 /* If either the open or the abort of the txn fails because that
1395 transaction is dead, just try to purge the thing. Else,
1396 there was either an error worth reporting, or not error at
1398 if (err && (err->apr_err == SVN_ERR_FS_TRANSACTION_DEAD))
1400 svn_error_clear(err);
1401 err = svn_fs_purge_txn(fs, txn_name_utf8, subpool);
1404 /* If we had a real from the txn open, abort, or purge, we clear
1405 that error and just report to the user that we had an issue
1406 with this particular txn. */
1409 svn_handle_error2(err, stderr, FALSE /* non-fatal */, "svnadmin: ");
1410 svn_error_clear(err);
1412 else if (! opt_state->quiet)
1414 SVN_ERR(svn_cmdline_printf(subpool, _("Transaction '%s' removed.\n"),
1419 svn_pool_destroy(subpool);
1421 return SVN_NO_ERROR;
1425 /* A helper for the 'setrevprop' and 'setlog' commands. Expects
1426 OPT_STATE->use_pre_revprop_change_hook and
1427 OPT_STATE->use_post_revprop_change_hook to be set appropriately. */
1428 static svn_error_t *
1429 set_revprop(const char *prop_name, const char *filename,
1430 struct svnadmin_opt_state *opt_state, apr_pool_t *pool)
1433 svn_string_t *prop_value = svn_string_create_empty(pool);
1434 svn_stringbuf_t *file_contents;
1436 SVN_ERR(svn_stringbuf_from_file2(&file_contents, filename, pool));
1438 prop_value->data = file_contents->data;
1439 prop_value->len = file_contents->len;
1441 SVN_ERR(svn_subst_translate_string2(&prop_value, NULL, NULL, prop_value,
1442 NULL, FALSE, pool, pool));
1444 /* Open the filesystem */
1445 SVN_ERR(open_repos(&repos, opt_state->repository_path, pool));
1447 /* If we are bypassing the hooks system, we just hit the filesystem
1449 SVN_ERR(svn_repos_fs_change_rev_prop4(
1450 repos, opt_state->start_revision.value.number,
1451 NULL, prop_name, NULL, prop_value,
1452 opt_state->use_pre_revprop_change_hook,
1453 opt_state->use_post_revprop_change_hook,
1456 return SVN_NO_ERROR;
1460 /* This implements `svn_opt_subcommand_t'. */
1461 static svn_error_t *
1462 subcommand_setrevprop(apr_getopt_t *os, void *baton, apr_pool_t *pool)
1464 struct svnadmin_opt_state *opt_state = baton;
1465 apr_array_header_t *args;
1466 const char *prop_name, *filename;
1468 /* Expect two more arguments: NAME FILE */
1469 SVN_ERR(parse_args(&args, os, 2, 2, pool));
1470 prop_name = APR_ARRAY_IDX(args, 0, const char *);
1471 filename = APR_ARRAY_IDX(args, 1, const char *);
1472 SVN_ERR(target_arg_to_dirent(&filename, filename, pool));
1474 if (opt_state->start_revision.kind != svn_opt_revision_number)
1475 return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
1476 _("Missing revision"));
1477 else if (opt_state->end_revision.kind != svn_opt_revision_unspecified)
1478 return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
1479 _("Only one revision allowed"));
1481 return set_revprop(prop_name, filename, opt_state, pool);
1485 /* This implements `svn_opt_subcommand_t'. */
1486 static svn_error_t *
1487 subcommand_setuuid(apr_getopt_t *os, void *baton, apr_pool_t *pool)
1489 struct svnadmin_opt_state *opt_state = baton;
1490 apr_array_header_t *args;
1493 const char *uuid = NULL;
1495 /* Expect zero or one more arguments: [UUID] */
1496 SVN_ERR(parse_args(&args, os, 0, 1, pool));
1497 if (args->nelts == 1)
1498 uuid = APR_ARRAY_IDX(args, 0, const char *);
1500 SVN_ERR(open_repos(&repos, opt_state->repository_path, pool));
1501 fs = svn_repos_fs(repos);
1502 return svn_fs_set_uuid(fs, uuid, pool);
1506 /* This implements `svn_opt_subcommand_t'. */
1507 static svn_error_t *
1508 subcommand_setlog(apr_getopt_t *os, void *baton, apr_pool_t *pool)
1510 struct svnadmin_opt_state *opt_state = baton;
1511 apr_array_header_t *args;
1512 const char *filename;
1514 /* Expect one more argument: FILE */
1515 SVN_ERR(parse_args(&args, os, 1, 1, pool));
1516 filename = APR_ARRAY_IDX(args, 0, const char *);
1517 SVN_ERR(target_arg_to_dirent(&filename, filename, pool));
1519 if (opt_state->start_revision.kind != svn_opt_revision_number)
1520 return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
1521 _("Missing revision"));
1522 else if (opt_state->end_revision.kind != svn_opt_revision_unspecified)
1523 return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
1524 _("Only one revision allowed"));
1526 /* set_revprop() responds only to pre-/post-revprop-change opts. */
1527 if (!opt_state->bypass_hooks)
1529 opt_state->use_pre_revprop_change_hook = TRUE;
1530 opt_state->use_post_revprop_change_hook = TRUE;
1533 return set_revprop(SVN_PROP_REVISION_LOG, filename, opt_state, pool);
1537 /* This implements 'svn_opt_subcommand_t'. */
1538 static svn_error_t *
1539 subcommand_pack(apr_getopt_t *os, void *baton, apr_pool_t *pool)
1541 struct svnadmin_opt_state *opt_state = baton;
1543 svn_stream_t *progress_stream = NULL;
1545 /* Expect no more arguments. */
1546 SVN_ERR(parse_args(NULL, os, 0, 0, pool));
1548 SVN_ERR(open_repos(&repos, opt_state->repository_path, pool));
1550 /* Progress feedback goes to STDOUT, unless they asked to suppress it. */
1551 if (! opt_state->quiet)
1552 progress_stream = recode_stream_create(stdout, pool);
1554 return svn_error_trace(
1555 svn_repos_fs_pack2(repos, !opt_state->quiet ? repos_notify_handler : NULL,
1556 progress_stream, check_cancel, NULL, pool));
1560 /* This implements `svn_opt_subcommand_t'. */
1561 static svn_error_t *
1562 subcommand_verify(apr_getopt_t *os, void *baton, apr_pool_t *pool)
1564 struct svnadmin_opt_state *opt_state = baton;
1567 svn_revnum_t youngest, lower, upper;
1568 svn_stream_t *progress_stream = NULL;
1570 /* Expect no more arguments. */
1571 SVN_ERR(parse_args(NULL, os, 0, 0, pool));
1573 if (opt_state->txn_id
1574 && (opt_state->start_revision.kind != svn_opt_revision_unspecified
1575 || opt_state->end_revision.kind != svn_opt_revision_unspecified))
1577 return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
1578 _("--revision (-r) and --transaction (-t) "
1579 "are mutually exclusive"));
1582 SVN_ERR(open_repos(&repos, opt_state->repository_path, pool));
1583 fs = svn_repos_fs(repos);
1584 SVN_ERR(svn_fs_youngest_rev(&youngest, fs, pool));
1587 if (opt_state->txn_id)
1590 svn_fs_root_t *root;
1592 SVN_ERR(svn_fs_open_txn(&txn, fs, opt_state->txn_id, pool));
1593 SVN_ERR(svn_fs_txn_root(&root, txn, pool));
1594 SVN_ERR(svn_fs_verify_root(root, pool));
1595 return SVN_NO_ERROR;
1601 /* Find the revision numbers at which to start and end. */
1602 SVN_ERR(get_revnum(&lower, &opt_state->start_revision,
1603 youngest, repos, pool));
1604 SVN_ERR(get_revnum(&upper, &opt_state->end_revision,
1605 youngest, repos, pool));
1607 if (upper == SVN_INVALID_REVNUM)
1612 if (! opt_state->quiet)
1613 progress_stream = recode_stream_create(stderr, pool);
1615 return svn_repos_verify_fs2(repos, lower, upper,
1617 ? repos_notify_handler : NULL,
1618 progress_stream, check_cancel, NULL, pool);
1621 /* This implements `svn_opt_subcommand_t'. */
1623 subcommand_hotcopy(apr_getopt_t *os, void *baton, apr_pool_t *pool)
1625 struct svnadmin_opt_state *opt_state = baton;
1626 apr_array_header_t *targets;
1627 const char *new_repos_path;
1629 /* Expect one more argument: NEW_REPOS_PATH */
1630 SVN_ERR(parse_args(&targets, os, 1, 1, pool));
1631 new_repos_path = APR_ARRAY_IDX(targets, 0, const char *);
1632 SVN_ERR(target_arg_to_dirent(&new_repos_path, new_repos_path, pool));
1634 return svn_repos_hotcopy2(opt_state->repository_path, new_repos_path,
1635 opt_state->clean_logs, opt_state->incremental,
1636 check_cancel, NULL, pool);
1639 /* This implements `svn_opt_subcommand_t'. */
1640 static svn_error_t *
1641 subcommand_lock(apr_getopt_t *os, void *baton, apr_pool_t *pool)
1643 struct svnadmin_opt_state *opt_state = baton;
1646 svn_fs_access_t *access;
1647 apr_array_header_t *args;
1648 const char *username;
1649 const char *lock_path;
1650 const char *comment_file_name;
1651 svn_stringbuf_t *file_contents;
1652 const char *lock_path_utf8;
1654 const char *lock_token = NULL;
1656 /* Expect three more arguments: PATH USERNAME COMMENT-FILE */
1657 SVN_ERR(parse_args(&args, os, 3, 4, pool));
1658 lock_path = APR_ARRAY_IDX(args, 0, const char *);
1659 username = APR_ARRAY_IDX(args, 1, const char *);
1660 comment_file_name = APR_ARRAY_IDX(args, 2, const char *);
1662 /* Expect one more optional argument: TOKEN */
1663 if (args->nelts == 4)
1664 lock_token = APR_ARRAY_IDX(args, 3, const char *);
1666 SVN_ERR(target_arg_to_dirent(&comment_file_name, comment_file_name, pool));
1668 SVN_ERR(open_repos(&repos, opt_state->repository_path, pool));
1669 fs = svn_repos_fs(repos);
1671 /* Create an access context describing the user. */
1672 SVN_ERR(svn_fs_create_access(&access, username, pool));
1674 /* Attach the access context to the filesystem. */
1675 SVN_ERR(svn_fs_set_access(fs, access));
1677 SVN_ERR(svn_stringbuf_from_file2(&file_contents, comment_file_name, pool));
1679 SVN_ERR(svn_utf_cstring_to_utf8(&lock_path_utf8, lock_path, pool));
1681 if (opt_state->bypass_hooks)
1682 SVN_ERR(svn_fs_lock(&lock, fs, lock_path_utf8,
1684 file_contents->data, /* comment */
1685 0, /* is_dav_comment */
1686 0, /* no expiration time. */
1690 SVN_ERR(svn_repos_fs_lock(&lock, repos, lock_path_utf8,
1692 file_contents->data, /* comment */
1693 0, /* is_dav_comment */
1694 0, /* no expiration time. */
1698 SVN_ERR(svn_cmdline_printf(pool, _("'%s' locked by user '%s'.\n"),
1699 lock_path, username));
1700 return SVN_NO_ERROR;
1703 static svn_error_t *
1704 subcommand_lslocks(apr_getopt_t *os, void *baton, apr_pool_t *pool)
1706 struct svnadmin_opt_state *opt_state = baton;
1707 apr_array_header_t *targets;
1709 const char *fs_path = "/";
1711 apr_hash_index_t *hi;
1713 SVN_ERR(svn_opt__args_to_target_array(&targets, os,
1714 apr_array_make(pool, 0,
1715 sizeof(const char *)),
1717 if (targets->nelts > 1)
1718 return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, 0,
1719 _("Too many arguments given"));
1721 fs_path = APR_ARRAY_IDX(targets, 0, const char *);
1723 SVN_ERR(open_repos(&repos, opt_state->repository_path, pool));
1725 /* Fetch all locks on or below the root directory. */
1726 SVN_ERR(svn_repos_fs_get_locks2(&locks, repos, fs_path, svn_depth_infinity,
1729 for (hi = apr_hash_first(pool, locks); hi; hi = apr_hash_next(hi))
1731 const char *cr_date, *exp_date = "";
1732 const char *path = svn__apr_hash_index_key(hi);
1733 svn_lock_t *lock = svn__apr_hash_index_val(hi);
1734 int comment_lines = 0;
1736 cr_date = svn_time_to_human_cstring(lock->creation_date, pool);
1738 if (lock->expiration_date)
1739 exp_date = svn_time_to_human_cstring(lock->expiration_date, pool);
1742 comment_lines = svn_cstring_count_newlines(lock->comment) + 1;
1744 SVN_ERR(svn_cmdline_printf(pool, _("Path: %s\n"), path));
1745 SVN_ERR(svn_cmdline_printf(pool, _("UUID Token: %s\n"), lock->token));
1746 SVN_ERR(svn_cmdline_printf(pool, _("Owner: %s\n"), lock->owner));
1747 SVN_ERR(svn_cmdline_printf(pool, _("Created: %s\n"), cr_date));
1748 SVN_ERR(svn_cmdline_printf(pool, _("Expires: %s\n"), exp_date));
1749 SVN_ERR(svn_cmdline_printf(pool,
1750 Q_("Comment (%i line):\n%s\n\n",
1751 "Comment (%i lines):\n%s\n\n",
1754 lock->comment ? lock->comment : ""));
1757 return SVN_NO_ERROR;
1762 static svn_error_t *
1763 subcommand_rmlocks(apr_getopt_t *os, void *baton, apr_pool_t *pool)
1765 struct svnadmin_opt_state *opt_state = baton;
1768 svn_fs_access_t *access;
1770 apr_array_header_t *args;
1772 const char *username;
1773 apr_pool_t *subpool = svn_pool_create(pool);
1775 SVN_ERR(open_repos(&repos, opt_state->repository_path, pool));
1776 fs = svn_repos_fs(repos);
1778 /* svn_fs_unlock() demands that some username be associated with the
1779 filesystem, so just use the UID of the person running 'svnadmin'.*/
1780 username = svn_user_get_name(pool);
1782 username = "administrator";
1784 /* Create an access context describing the current user. */
1785 SVN_ERR(svn_fs_create_access(&access, username, pool));
1787 /* Attach the access context to the filesystem. */
1788 SVN_ERR(svn_fs_set_access(fs, access));
1790 /* Parse out any options. */
1791 SVN_ERR(svn_opt_parse_all_args(&args, os, pool));
1793 /* Our usage requires at least one FS path. */
1794 if (args->nelts == 0)
1795 return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, 0,
1796 _("No paths to unlock provided"));
1798 /* All the rest of the arguments are paths from which to remove locks. */
1799 for (i = 0; i < args->nelts; i++)
1801 const char *lock_path = APR_ARRAY_IDX(args, i, const char *);
1802 const char *lock_path_utf8;
1805 SVN_ERR(svn_utf_cstring_to_utf8(&lock_path_utf8, lock_path, subpool));
1807 /* Fetch the path's svn_lock_t. */
1808 err = svn_fs_get_lock(&lock, fs, lock_path_utf8, subpool);
1813 SVN_ERR(svn_cmdline_printf(subpool,
1814 _("Path '%s' isn't locked.\n"),
1819 /* Now forcibly destroy the lock. */
1820 err = svn_fs_unlock(fs, lock_path_utf8,
1821 lock->token, 1 /* force */, subpool);
1825 SVN_ERR(svn_cmdline_printf(subpool,
1826 _("Removed lock on '%s'.\n"), lock->path));
1831 /* Print the error, but move on to the next lock. */
1832 svn_handle_error2(err, stderr, FALSE /* non-fatal */, "svnadmin: ");
1833 svn_error_clear(err);
1836 svn_pool_clear(subpool);
1839 svn_pool_destroy(subpool);
1840 return SVN_NO_ERROR;
1844 /* This implements `svn_opt_subcommand_t'. */
1845 static svn_error_t *
1846 subcommand_unlock(apr_getopt_t *os, void *baton, apr_pool_t *pool)
1848 struct svnadmin_opt_state *opt_state = baton;
1851 svn_fs_access_t *access;
1852 apr_array_header_t *args;
1853 const char *username;
1854 const char *lock_path;
1855 const char *lock_path_utf8;
1856 const char *lock_token = NULL;
1858 /* Expect three more arguments: PATH USERNAME TOKEN */
1859 SVN_ERR(parse_args(&args, os, 3, 3, pool));
1860 lock_path = APR_ARRAY_IDX(args, 0, const char *);
1861 username = APR_ARRAY_IDX(args, 1, const char *);
1862 lock_token = APR_ARRAY_IDX(args, 2, const char *);
1864 /* Open the repos/FS, and associate an access context containing
1866 SVN_ERR(open_repos(&repos, opt_state->repository_path, pool));
1867 fs = svn_repos_fs(repos);
1868 SVN_ERR(svn_fs_create_access(&access, username, pool));
1869 SVN_ERR(svn_fs_set_access(fs, access));
1871 SVN_ERR(svn_utf_cstring_to_utf8(&lock_path_utf8, lock_path, pool));
1872 if (opt_state->bypass_hooks)
1873 SVN_ERR(svn_fs_unlock(fs, lock_path_utf8, lock_token,
1876 SVN_ERR(svn_repos_fs_unlock(repos, lock_path_utf8, lock_token,
1879 SVN_ERR(svn_cmdline_printf(pool, _("'%s' unlocked by user '%s'.\n"),
1880 lock_path, username));
1881 return SVN_NO_ERROR;
1885 /* This implements `svn_opt_subcommand_t'. */
1886 static svn_error_t *
1887 subcommand_upgrade(apr_getopt_t *os, void *baton, apr_pool_t *pool)
1890 struct svnadmin_opt_state *opt_state = baton;
1891 svn_stream_t *stdout_stream;
1893 /* Expect no more arguments. */
1894 SVN_ERR(parse_args(NULL, os, 0, 0, pool));
1896 SVN_ERR(svn_stream_for_stdout(&stdout_stream, pool));
1898 /* Restore default signal handlers. */
1899 setup_cancellation_signals(SIG_DFL);
1901 err = svn_repos_upgrade2(opt_state->repository_path, TRUE,
1902 repos_notify_handler, stdout_stream, pool);
1905 if (APR_STATUS_IS_EAGAIN(err->apr_err))
1907 svn_error_clear(err);
1909 if (! opt_state->wait)
1910 return svn_error_create(SVN_ERR_REPOS_LOCKED, NULL,
1911 _("Failed to get exclusive repository "
1912 "access; perhaps another process\n"
1913 "such as httpd, svnserve or svn "
1915 SVN_ERR(svn_cmdline_printf(pool,
1916 _("Waiting on repository lock; perhaps"
1917 " another process has it open?\n")));
1918 SVN_ERR(svn_cmdline_fflush(stdout));
1919 SVN_ERR(svn_repos_upgrade2(opt_state->repository_path, FALSE,
1920 repos_notify_handler, stdout_stream,
1923 else if (err->apr_err == SVN_ERR_FS_UNSUPPORTED_UPGRADE)
1925 return svn_error_quick_wrap(err,
1926 _("Upgrade of this repository's underlying versioned "
1927 "filesystem is not supported; consider "
1928 "dumping and loading the data elsewhere"));
1930 else if (err->apr_err == SVN_ERR_REPOS_UNSUPPORTED_UPGRADE)
1932 return svn_error_quick_wrap(err,
1933 _("Upgrade of this repository is not supported; consider "
1934 "dumping and loading the data elsewhere"));
1939 SVN_ERR(svn_cmdline_printf(pool, _("\nUpgrade completed.\n")));
1940 return SVN_NO_ERROR;
1947 /* Report and clear the error ERR, and return EXIT_FAILURE. */
1948 #define EXIT_ERROR(err) \
1949 svn_cmdline_handle_exit_error(err, NULL, "svnadmin: ")
1951 /* A redefinition of the public SVN_INT_ERR macro, that suppresses the
1952 * error message if it is SVN_ERR_IO_PIPE_WRITE_ERROR, amd with the
1953 * program name 'svnadmin' instead of 'svn'. */
1955 #define SVN_INT_ERR(expr) \
1957 svn_error_t *svn_err__temp = (expr); \
1958 if (svn_err__temp) \
1959 return EXIT_ERROR(svn_err__temp); \
1963 sub_main(int argc, const char *argv[], apr_pool_t *pool)
1966 apr_status_t apr_err;
1968 const svn_opt_subcommand_desc2_t *subcommand = NULL;
1969 struct svnadmin_opt_state opt_state = { 0 };
1972 apr_array_header_t *received_opts;
1974 svn_boolean_t dash_F_arg = FALSE;
1976 received_opts = apr_array_make(pool, SVN_OPT_MAX_OPTIONS, sizeof(int));
1978 /* Check library versions */
1979 SVN_INT_ERR(check_lib_versions());
1981 /* Initialize the FS library. */
1982 SVN_INT_ERR(svn_fs_initialize(pool));
1986 SVN_INT_ERR(subcommand_help(NULL, NULL, pool));
1987 return EXIT_FAILURE;
1990 /* Initialize opt_state. */
1991 opt_state.start_revision.kind = svn_opt_revision_unspecified;
1992 opt_state.end_revision.kind = svn_opt_revision_unspecified;
1993 opt_state.memory_cache_size = svn_cache_config_get()->cache_size;
1995 /* Parse options. */
1996 SVN_INT_ERR(svn_cmdline__getopt_init(&os, argc, argv, pool));
2002 const char *opt_arg;
2003 const char *utf8_opt_arg;
2005 /* Parse the next option. */
2006 apr_err = apr_getopt_long(os, options_table, &opt_id, &opt_arg);
2007 if (APR_STATUS_IS_EOF(apr_err))
2011 SVN_INT_ERR(subcommand_help(NULL, NULL, pool));
2012 return EXIT_FAILURE;
2015 /* Stash the option code in an array before parsing it. */
2016 APR_ARRAY_PUSH(received_opts, int) = opt_id;
2021 if (opt_state.start_revision.kind != svn_opt_revision_unspecified)
2023 err = svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
2024 _("Multiple revision arguments encountered; "
2025 "try '-r N:M' instead of '-r N -r M'"));
2026 return EXIT_ERROR(err);
2028 if (svn_opt_parse_revision(&(opt_state.start_revision),
2029 &(opt_state.end_revision),
2030 opt_arg, pool) != 0)
2032 err = svn_utf_cstring_to_utf8(&utf8_opt_arg, opt_arg,
2036 err = svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
2037 _("Syntax error in revision argument '%s'"),
2039 return EXIT_ERROR(err);
2044 opt_state.txn_id = opt_arg;
2048 opt_state.quiet = TRUE;
2052 opt_state.help = TRUE;
2055 opt_state.memory_cache_size
2056 = 0x100000 * apr_strtoi64(opt_arg, NULL, 0);
2059 SVN_INT_ERR(svn_utf_cstring_to_utf8(&utf8_opt_arg, opt_arg, pool));
2060 SVN_INT_ERR(svn_stringbuf_from_file2(&(opt_state.filedata),
2061 utf8_opt_arg, pool));
2063 case svnadmin__version:
2064 opt_state.version = TRUE;
2066 case svnadmin__incremental:
2067 opt_state.incremental = TRUE;
2069 case svnadmin__deltas:
2070 opt_state.use_deltas = TRUE;
2072 case svnadmin__ignore_uuid:
2073 opt_state.uuid_action = svn_repos_load_uuid_ignore;
2075 case svnadmin__force_uuid:
2076 opt_state.uuid_action = svn_repos_load_uuid_force;
2078 case svnadmin__pre_1_4_compatible:
2079 opt_state.pre_1_4_compatible = TRUE;
2081 case svnadmin__pre_1_5_compatible:
2082 opt_state.pre_1_5_compatible = TRUE;
2084 case svnadmin__pre_1_6_compatible:
2085 opt_state.pre_1_6_compatible = TRUE;
2087 case svnadmin__compatible_version:
2089 svn_version_t latest = { SVN_VER_MAJOR, SVN_VER_MINOR,
2090 SVN_VER_PATCH, NULL };
2091 svn_version_t *compatible_version;
2093 /* Parse the version string which carries our target
2095 SVN_INT_ERR(svn_version__parse_version_string(&compatible_version,
2098 /* We can't create repository with a version older than 1.0.0. */
2099 if (! svn_version__at_least(compatible_version, 1, 0, 0))
2101 err = svn_error_createf(SVN_ERR_UNSUPPORTED_FEATURE, NULL,
2102 _("Cannot create pre-1.0-compatible "
2104 return EXIT_ERROR(err);
2107 /* We can't create repository with a version newer than what
2108 the running version of Subversion supports. */
2109 if (! svn_version__at_least(&latest,
2110 compatible_version->major,
2111 compatible_version->minor,
2112 compatible_version->patch))
2114 err = svn_error_createf(SVN_ERR_UNSUPPORTED_FEATURE, NULL,
2115 _("Cannot guarantee compatibility "
2116 "beyond the current running version "
2119 return EXIT_ERROR(err);
2122 opt_state.compatible_version = compatible_version;
2125 case svnadmin__fs_type:
2126 SVN_INT_ERR(svn_utf_cstring_to_utf8(&opt_state.fs_type, opt_arg, pool));
2128 case svnadmin__parent_dir:
2129 SVN_INT_ERR(svn_utf_cstring_to_utf8(&opt_state.parent_dir, opt_arg,
2131 opt_state.parent_dir
2132 = svn_dirent_internal_style(opt_state.parent_dir, pool);
2134 case svnadmin__use_pre_commit_hook:
2135 opt_state.use_pre_commit_hook = TRUE;
2137 case svnadmin__use_post_commit_hook:
2138 opt_state.use_post_commit_hook = TRUE;
2140 case svnadmin__use_pre_revprop_change_hook:
2141 opt_state.use_pre_revprop_change_hook = TRUE;
2143 case svnadmin__use_post_revprop_change_hook:
2144 opt_state.use_post_revprop_change_hook = TRUE;
2146 case svnadmin__bdb_txn_nosync:
2147 opt_state.bdb_txn_nosync = TRUE;
2149 case svnadmin__bdb_log_keep:
2150 opt_state.bdb_log_keep = TRUE;
2152 case svnadmin__bypass_hooks:
2153 opt_state.bypass_hooks = TRUE;
2155 case svnadmin__bypass_prop_validation:
2156 opt_state.bypass_prop_validation = TRUE;
2158 case svnadmin__clean_logs:
2159 opt_state.clean_logs = TRUE;
2161 case svnadmin__config_dir:
2162 SVN_INT_ERR(svn_utf_cstring_to_utf8(&utf8_opt_arg, opt_arg, pool));
2163 opt_state.config_dir =
2164 apr_pstrdup(pool, svn_dirent_canonicalize(utf8_opt_arg, pool));
2166 case svnadmin__wait:
2167 opt_state.wait = TRUE;
2171 SVN_INT_ERR(subcommand_help(NULL, NULL, pool));
2172 return EXIT_FAILURE;
2174 } /* close `switch' */
2175 } /* close `while' */
2177 /* If the user asked for help, then the rest of the arguments are
2178 the names of subcommands to get help on (if any), or else they're
2179 just typos/mistakes. Whatever the case, the subcommand to
2180 actually run is subcommand_help(). */
2182 subcommand = svn_opt_get_canonical_subcommand2(cmd_table, "help");
2184 /* If we're not running the `help' subcommand, then look for a
2185 subcommand in the first argument. */
2186 if (subcommand == NULL)
2188 if (os->ind >= os->argc)
2190 if (opt_state.version)
2192 /* Use the "help" subcommand to handle the "--version" option. */
2193 static const svn_opt_subcommand_desc2_t pseudo_cmd =
2194 { "--version", subcommand_help, {0}, "",
2195 {svnadmin__version, /* must accept its own option */
2199 subcommand = &pseudo_cmd;
2203 svn_error_clear(svn_cmdline_fprintf(stderr, pool,
2204 _("subcommand argument required\n")));
2205 SVN_INT_ERR(subcommand_help(NULL, NULL, pool));
2206 return EXIT_FAILURE;
2211 const char *first_arg = os->argv[os->ind++];
2212 subcommand = svn_opt_get_canonical_subcommand2(cmd_table, first_arg);
2213 if (subcommand == NULL)
2215 const char *first_arg_utf8;
2216 SVN_INT_ERR(svn_utf_cstring_to_utf8(&first_arg_utf8,
2219 svn_cmdline_fprintf(stderr, pool,
2220 _("Unknown subcommand: '%s'\n"),
2222 SVN_INT_ERR(subcommand_help(NULL, NULL, pool));
2223 return EXIT_FAILURE;
2228 /* Every subcommand except `help' and `freeze' with '-F' require a
2229 second argument -- the repository path. Parse it out here and
2230 store it in opt_state. */
2231 if (!(subcommand->cmd_func == subcommand_help
2232 || (subcommand->cmd_func == subcommand_freeze && dash_F_arg)))
2234 const char *repos_path = NULL;
2236 if (os->ind >= os->argc)
2238 err = svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
2239 _("Repository argument required"));
2240 return EXIT_ERROR(err);
2243 if ((err = svn_utf_cstring_to_utf8(&repos_path,
2244 os->argv[os->ind++], pool)))
2246 return EXIT_ERROR(err);
2249 if (svn_path_is_url(repos_path))
2251 err = svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
2252 _("'%s' is a URL when it should be a "
2253 "local path"), repos_path);
2254 return EXIT_ERROR(err);
2257 opt_state.repository_path = svn_dirent_internal_style(repos_path, pool);
2260 /* Check that the subcommand wasn't passed any inappropriate options. */
2261 for (i = 0; i < received_opts->nelts; i++)
2263 opt_id = APR_ARRAY_IDX(received_opts, i, int);
2265 /* All commands implicitly accept --help, so just skip over this
2266 when we see it. Note that we don't want to include this option
2267 in their "accepted options" list because it would be awfully
2268 redundant to display it in every commands' help text. */
2269 if (opt_id == 'h' || opt_id == '?')
2272 if (! svn_opt_subcommand_takes_option3(subcommand, opt_id, NULL))
2275 const apr_getopt_option_t *badopt =
2276 svn_opt_get_option_from_code2(opt_id, options_table, subcommand,
2278 svn_opt_format_option(&optstr, badopt, FALSE, pool);
2279 if (subcommand->name[0] == '-')
2280 SVN_INT_ERR(subcommand_help(NULL, NULL, pool));
2282 svn_error_clear(svn_cmdline_fprintf(stderr, pool
2283 , _("Subcommand '%s' doesn't accept option '%s'\n"
2284 "Type 'svnadmin help %s' for usage.\n"),
2285 subcommand->name, optstr, subcommand->name));
2286 return EXIT_FAILURE;
2290 /* Set up our cancellation support. */
2291 setup_cancellation_signals(signal_handler);
2294 /* Disable SIGPIPE generation for the platforms that have it. */
2295 apr_signal(SIGPIPE, SIG_IGN);
2299 /* Disable SIGXFSZ generation for the platforms that have it, otherwise
2300 * working with large files when compiled against an APR that doesn't have
2301 * large file support will crash the program, which is uncool. */
2302 apr_signal(SIGXFSZ, SIG_IGN);
2305 /* Configure FSFS caches for maximum efficiency with svnadmin.
2306 * Also, apply the respective command line parameters, if given. */
2308 svn_cache_config_t settings = *svn_cache_config_get();
2310 settings.cache_size = opt_state.memory_cache_size;
2311 settings.single_threaded = TRUE;
2313 svn_cache_config_set(&settings);
2316 /* Run the subcommand. */
2317 err = (*subcommand->cmd_func)(os, &opt_state, pool);
2320 /* For argument-related problems, suggest using the 'help'
2322 if (err->apr_err == SVN_ERR_CL_INSUFFICIENT_ARGS
2323 || err->apr_err == SVN_ERR_CL_ARG_PARSING_ERROR)
2325 err = svn_error_quick_wrap(err,
2326 _("Try 'svnadmin help' for more info"));
2328 return EXIT_ERROR(err);
2332 /* Ensure that everything is written to stdout, so the user will
2333 see any print errors. */
2334 err = svn_cmdline_fflush(stdout);
2337 return EXIT_ERROR(err);
2339 return EXIT_SUCCESS;
2344 main(int argc, const char *argv[])
2349 /* Initialize the app. */
2350 if (svn_cmdline_init("svnadmin", stderr) != EXIT_SUCCESS)
2351 return EXIT_FAILURE;
2353 /* Create our top-level pool. Use a separate mutexless allocator,
2354 * given this application is single threaded.
2356 pool = apr_allocator_owner_get(svn_pool_create_allocator(FALSE));
2358 exit_code = sub_main(argc, argv, pool);
2360 svn_pool_destroy(pool);