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_list(&my_version, checklist);
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 cmdline_stream_printf(svn_stream_t *stream,
764 __attribute__((format(printf, 3, 4)));
767 cmdline_stream_printf(svn_stream_t *stream,
778 message = apr_pvsprintf(pool, fmt, ap);
781 err = svn_cmdline_cstring_from_utf8(&out, message, pool);
785 svn_error_clear(err);
786 out = svn_cmdline_cstring_from_utf8_fuzzy(message, pool);
789 svn_error_clear(svn_stream_puts(stream, out));
793 /* Implementation of svn_repos_notify_func_t to wrap the output to a
794 response stream for svn_repos_dump_fs2() and svn_repos_verify_fs() */
796 repos_notify_handler(void *baton,
797 const svn_repos_notify_t *notify,
798 apr_pool_t *scratch_pool)
800 svn_stream_t *feedback_stream = baton;
802 switch (notify->action)
804 case svn_repos_notify_warning:
805 cmdline_stream_printf(feedback_stream, scratch_pool,
806 "WARNING 0x%04x: %s\n", notify->warning,
807 notify->warning_str);
810 case svn_repos_notify_dump_rev_end:
811 cmdline_stream_printf(feedback_stream, scratch_pool,
812 _("* Dumped revision %ld.\n"),
816 case svn_repos_notify_verify_rev_end:
817 cmdline_stream_printf(feedback_stream, scratch_pool,
818 _("* Verified revision %ld.\n"),
822 case svn_repos_notify_verify_rev_structure:
823 if (notify->revision == SVN_INVALID_REVNUM)
824 cmdline_stream_printf(feedback_stream, scratch_pool,
825 _("* Verifying repository metadata ...\n"));
827 cmdline_stream_printf(feedback_stream, scratch_pool,
828 _("* Verifying metadata at revision %ld ...\n"),
832 case svn_repos_notify_pack_shard_start:
834 const char *shardstr = apr_psprintf(scratch_pool,
837 cmdline_stream_printf(feedback_stream, scratch_pool,
838 _("Packing revisions in shard %s..."),
843 case svn_repos_notify_pack_shard_end:
844 cmdline_stream_printf(feedback_stream, scratch_pool, _("done.\n"));
847 case svn_repos_notify_pack_shard_start_revprop:
849 const char *shardstr = apr_psprintf(scratch_pool,
852 cmdline_stream_printf(feedback_stream, scratch_pool,
853 _("Packing revprops in shard %s..."),
858 case svn_repos_notify_pack_shard_end_revprop:
859 cmdline_stream_printf(feedback_stream, scratch_pool, _("done.\n"));
862 case svn_repos_notify_load_txn_committed:
863 if (notify->old_revision == SVN_INVALID_REVNUM)
865 cmdline_stream_printf(feedback_stream, scratch_pool,
866 _("\n------- Committed revision %ld >>>\n\n"),
867 notify->new_revision);
871 cmdline_stream_printf(feedback_stream, scratch_pool,
872 _("\n------- Committed new rev %ld"
873 " (loaded from original rev %ld"
874 ") >>>\n\n"), notify->new_revision,
875 notify->old_revision);
879 case svn_repos_notify_load_node_start:
881 switch (notify->node_action)
883 case svn_node_action_change:
884 cmdline_stream_printf(feedback_stream, scratch_pool,
885 _(" * editing path : %s ..."),
889 case svn_node_action_delete:
890 cmdline_stream_printf(feedback_stream, scratch_pool,
891 _(" * deleting path : %s ..."),
895 case svn_node_action_add:
896 cmdline_stream_printf(feedback_stream, scratch_pool,
897 _(" * adding path : %s ..."),
901 case svn_node_action_replace:
902 cmdline_stream_printf(feedback_stream, scratch_pool,
903 _(" * replacing path : %s ..."),
911 case svn_repos_notify_load_node_done:
912 cmdline_stream_printf(feedback_stream, scratch_pool, _(" done.\n"));
915 case svn_repos_notify_load_copied_node:
916 cmdline_stream_printf(feedback_stream, scratch_pool, "COPIED...");
919 case svn_repos_notify_load_txn_start:
920 cmdline_stream_printf(feedback_stream, scratch_pool,
921 _("<<< Started new transaction, based on "
922 "original revision %ld\n"),
923 notify->old_revision);
926 case svn_repos_notify_load_skipped_rev:
927 cmdline_stream_printf(feedback_stream, scratch_pool,
928 _("<<< Skipped original revision %ld\n"),
929 notify->old_revision);
932 case svn_repos_notify_load_normalized_mergeinfo:
933 cmdline_stream_printf(feedback_stream, scratch_pool,
934 _(" removing '\\r' from %s ..."),
938 case svn_repos_notify_mutex_acquired:
939 /* Enable cancellation signal handlers. */
940 setup_cancellation_signals(signal_handler);
943 case svn_repos_notify_recover_start:
944 cmdline_stream_printf(feedback_stream, scratch_pool,
945 _("Repository lock acquired.\n"
946 "Please wait; recovering the"
947 " repository may take some time...\n"));
950 case svn_repos_notify_upgrade_start:
951 cmdline_stream_printf(feedback_stream, scratch_pool,
952 _("Repository lock acquired.\n"
953 "Please wait; upgrading the"
954 " repository may take some time...\n"));
963 /* Baton for recode_write(). */
964 struct recode_write_baton
970 /* This implements the 'svn_write_fn_t' interface.
972 Write DATA to ((struct recode_write_baton *) BATON)->out, in the
973 console encoding, using svn_cmdline_fprintf(). DATA is a
974 UTF8-encoded C string, therefore ignore LEN.
976 ### This recoding mechanism might want to be abstracted into
977 ### svn_io.h or svn_cmdline.h, if it proves useful elsewhere. */
978 static svn_error_t *recode_write(void *baton,
982 struct recode_write_baton *rwb = baton;
983 svn_pool_clear(rwb->pool);
984 return svn_cmdline_fputs(data, rwb->out, rwb->pool);
987 /* Create a stream, to write to STD_STREAM, that uses recode_write()
988 to perform UTF-8 to console encoding translation. */
989 static svn_stream_t *
990 recode_stream_create(FILE *std_stream, apr_pool_t *pool)
992 struct recode_write_baton *std_stream_rwb =
993 apr_palloc(pool, sizeof(struct recode_write_baton));
995 svn_stream_t *rw_stream = svn_stream_create(std_stream_rwb, pool);
996 std_stream_rwb->pool = svn_pool_create(pool);
997 std_stream_rwb->out = std_stream;
998 svn_stream_set_write(rw_stream, recode_write);
1003 /* This implements `svn_opt_subcommand_t'. */
1004 static svn_error_t *
1005 subcommand_dump(apr_getopt_t *os, void *baton, apr_pool_t *pool)
1007 struct svnadmin_opt_state *opt_state = baton;
1010 svn_stream_t *stdout_stream;
1011 svn_revnum_t lower = SVN_INVALID_REVNUM, upper = SVN_INVALID_REVNUM;
1012 svn_revnum_t youngest;
1013 svn_stream_t *progress_stream = NULL;
1015 /* Expect no more arguments. */
1016 SVN_ERR(parse_args(NULL, os, 0, 0, pool));
1018 SVN_ERR(open_repos(&repos, opt_state->repository_path, pool));
1019 fs = svn_repos_fs(repos);
1020 SVN_ERR(svn_fs_youngest_rev(&youngest, fs, pool));
1022 /* Find the revision numbers at which to start and end. */
1023 SVN_ERR(get_revnum(&lower, &opt_state->start_revision,
1024 youngest, repos, pool));
1025 SVN_ERR(get_revnum(&upper, &opt_state->end_revision,
1026 youngest, repos, pool));
1028 /* Fill in implied revisions if necessary. */
1029 if (lower == SVN_INVALID_REVNUM)
1034 else if (upper == SVN_INVALID_REVNUM)
1040 return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
1041 _("First revision cannot be higher than second"));
1043 SVN_ERR(svn_stream_for_stdout(&stdout_stream, pool));
1045 /* Progress feedback goes to STDERR, unless they asked to suppress it. */
1046 if (! opt_state->quiet)
1047 progress_stream = recode_stream_create(stderr, pool);
1049 SVN_ERR(svn_repos_dump_fs3(repos, stdout_stream, lower, upper,
1050 opt_state->incremental, opt_state->use_deltas,
1051 !opt_state->quiet ? repos_notify_handler : NULL,
1052 progress_stream, check_cancel, NULL, pool));
1054 return SVN_NO_ERROR;
1057 struct freeze_baton_t {
1058 const char *command;
1063 /* Implements svn_repos_freeze_func_t */
1064 static svn_error_t *
1065 freeze_body(void *baton,
1068 struct freeze_baton_t *b = baton;
1069 apr_status_t apr_err;
1070 apr_file_t *infile, *outfile, *errfile;
1072 apr_err = apr_file_open_stdin(&infile, pool);
1074 return svn_error_wrap_apr(apr_err, "Can't open stdin");
1075 apr_err = apr_file_open_stdout(&outfile, pool);
1077 return svn_error_wrap_apr(apr_err, "Can't open stdout");
1078 apr_err = apr_file_open_stderr(&errfile, pool);
1080 return svn_error_wrap_apr(apr_err, "Can't open stderr");
1082 SVN_ERR(svn_io_run_cmd(NULL, b->command, b->args, &b->status,
1084 infile, outfile, errfile, pool));
1086 return SVN_NO_ERROR;
1089 static svn_error_t *
1090 subcommand_freeze(apr_getopt_t *os, void *baton, apr_pool_t *pool)
1092 struct svnadmin_opt_state *opt_state = baton;
1093 apr_array_header_t *paths;
1094 apr_array_header_t *args;
1096 struct freeze_baton_t b;
1098 SVN_ERR(svn_opt_parse_all_args(&args, os, pool));
1101 return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, 0,
1102 _("No program provided"));
1104 if (!opt_state->filedata)
1106 /* One repository on the command line. */
1107 paths = apr_array_make(pool, 1, sizeof(const char *));
1108 APR_ARRAY_PUSH(paths, const char *) = opt_state->repository_path;
1112 /* All repositories in filedata. */
1113 paths = svn_cstring_split(opt_state->filedata->data, "\n", FALSE, pool);
1116 b.command = APR_ARRAY_IDX(args, 0, const char *);
1117 b.args = apr_palloc(pool, sizeof(char *) * args->nelts + 1);
1118 for (i = 0; i < args->nelts; ++i)
1119 b.args[i] = APR_ARRAY_IDX(args, i, const char *);
1120 b.args[args->nelts] = NULL;
1122 SVN_ERR(svn_repos_freeze(paths, freeze_body, &b, pool));
1124 /* Make any non-zero status visible to the user. */
1128 return SVN_NO_ERROR;
1132 /* This implements `svn_opt_subcommand_t'. */
1133 static svn_error_t *
1134 subcommand_help(apr_getopt_t *os, void *baton, apr_pool_t *pool)
1136 struct svnadmin_opt_state *opt_state = baton;
1137 const char *header =
1138 _("general usage: svnadmin SUBCOMMAND REPOS_PATH [ARGS & OPTIONS ...]\n"
1139 "Type 'svnadmin help <subcommand>' for help on a specific subcommand.\n"
1140 "Type 'svnadmin --version' to see the program version and FS modules.\n"
1142 "Available subcommands:\n");
1144 const char *fs_desc_start
1145 = _("The following repository back-end (FS) modules are available:\n\n");
1147 svn_stringbuf_t *version_footer;
1149 version_footer = svn_stringbuf_create(fs_desc_start, pool);
1150 SVN_ERR(svn_fs_print_modules(version_footer, pool));
1152 SVN_ERR(svn_opt_print_help4(os, "svnadmin",
1153 opt_state ? opt_state->version : FALSE,
1154 opt_state ? opt_state->quiet : FALSE,
1155 /*###opt_state ? opt_state->verbose :*/ FALSE,
1156 version_footer->data,
1157 header, cmd_table, options_table, NULL, NULL,
1160 return SVN_NO_ERROR;
1164 /* Set *REVNUM to the revision number of a numeric REV, or to
1165 SVN_INVALID_REVNUM if REV is unspecified. */
1166 static svn_error_t *
1167 optrev_to_revnum(svn_revnum_t *revnum, const svn_opt_revision_t *opt_rev)
1169 if (opt_rev->kind == svn_opt_revision_number)
1171 *revnum = opt_rev->value.number;
1172 if (! SVN_IS_VALID_REVNUM(*revnum))
1173 return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
1174 _("Invalid revision number (%ld) specified"),
1177 else if (opt_rev->kind == svn_opt_revision_unspecified)
1179 *revnum = SVN_INVALID_REVNUM;
1183 return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
1184 _("Non-numeric revision specified"));
1186 return SVN_NO_ERROR;
1190 /* This implements `svn_opt_subcommand_t'. */
1191 static svn_error_t *
1192 subcommand_load(apr_getopt_t *os, void *baton, apr_pool_t *pool)
1195 struct svnadmin_opt_state *opt_state = baton;
1197 svn_revnum_t lower = SVN_INVALID_REVNUM, upper = SVN_INVALID_REVNUM;
1198 svn_stream_t *stdin_stream, *stdout_stream = NULL;
1200 /* Expect no more arguments. */
1201 SVN_ERR(parse_args(NULL, os, 0, 0, pool));
1203 /* Find the revision numbers at which to start and end. We only
1204 support a limited set of revision kinds: number and unspecified. */
1205 SVN_ERR(optrev_to_revnum(&lower, &opt_state->start_revision));
1206 SVN_ERR(optrev_to_revnum(&upper, &opt_state->end_revision));
1208 /* Fill in implied revisions if necessary. */
1209 if ((upper == SVN_INVALID_REVNUM) && (lower != SVN_INVALID_REVNUM))
1213 else if ((upper != SVN_INVALID_REVNUM) && (lower == SVN_INVALID_REVNUM))
1218 /* Ensure correct range ordering. */
1221 return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
1222 _("First revision cannot be higher than second"));
1225 SVN_ERR(open_repos(&repos, opt_state->repository_path, pool));
1227 /* Read the stream from STDIN. Users can redirect a file. */
1228 SVN_ERR(svn_stream_for_stdin(&stdin_stream, pool));
1230 /* Progress feedback goes to STDOUT, unless they asked to suppress it. */
1231 if (! opt_state->quiet)
1232 stdout_stream = recode_stream_create(stdout, pool);
1234 err = svn_repos_load_fs4(repos, stdin_stream, lower, upper,
1235 opt_state->uuid_action, opt_state->parent_dir,
1236 opt_state->use_pre_commit_hook,
1237 opt_state->use_post_commit_hook,
1238 !opt_state->bypass_prop_validation,
1239 opt_state->quiet ? NULL : repos_notify_handler,
1240 stdout_stream, check_cancel, NULL, pool);
1241 if (err && err->apr_err == SVN_ERR_BAD_PROPERTY_VALUE)
1242 return svn_error_quick_wrap(err,
1243 _("Invalid property value found in "
1244 "dumpstream; consider repairing the source "
1245 "or using --bypass-prop-validation while "
1251 /* This implements `svn_opt_subcommand_t'. */
1252 static svn_error_t *
1253 subcommand_lstxns(apr_getopt_t *os, void *baton, apr_pool_t *pool)
1255 struct svnadmin_opt_state *opt_state = baton;
1258 apr_array_header_t *txns;
1261 /* Expect no more arguments. */
1262 SVN_ERR(parse_args(NULL, os, 0, 0, pool));
1264 SVN_ERR(open_repos(&repos, opt_state->repository_path, pool));
1265 fs = svn_repos_fs(repos);
1266 SVN_ERR(svn_fs_list_transactions(&txns, fs, pool));
1268 /* Loop, printing revisions. */
1269 for (i = 0; i < txns->nelts; i++)
1271 SVN_ERR(svn_cmdline_printf(pool, "%s\n",
1272 APR_ARRAY_IDX(txns, i, const char *)));
1275 return SVN_NO_ERROR;
1279 /* This implements `svn_opt_subcommand_t'. */
1280 static svn_error_t *
1281 subcommand_recover(apr_getopt_t *os, void *baton, apr_pool_t *pool)
1283 svn_revnum_t youngest_rev;
1286 struct svnadmin_opt_state *opt_state = baton;
1287 svn_stream_t *stdout_stream;
1289 /* Expect no more arguments. */
1290 SVN_ERR(parse_args(NULL, os, 0, 0, pool));
1292 SVN_ERR(svn_stream_for_stdout(&stdout_stream, pool));
1294 /* Restore default signal handlers until after we have acquired the
1295 * exclusive lock so that the user interrupt before we actually
1296 * touch the repository. */
1297 setup_cancellation_signals(SIG_DFL);
1299 err = svn_repos_recover4(opt_state->repository_path, TRUE,
1300 repos_notify_handler, stdout_stream,
1301 check_cancel, NULL, pool);
1304 if (! APR_STATUS_IS_EAGAIN(err->apr_err))
1306 svn_error_clear(err);
1307 if (! opt_state->wait)
1308 return svn_error_create(SVN_ERR_REPOS_LOCKED, NULL,
1309 _("Failed to get exclusive repository "
1310 "access; perhaps another process\n"
1311 "such as httpd, svnserve or svn "
1313 SVN_ERR(svn_cmdline_printf(pool,
1314 _("Waiting on repository lock; perhaps"
1315 " another process has it open?\n")));
1316 SVN_ERR(svn_cmdline_fflush(stdout));
1317 SVN_ERR(svn_repos_recover4(opt_state->repository_path, FALSE,
1318 repos_notify_handler, stdout_stream,
1319 check_cancel, NULL, pool));
1322 SVN_ERR(svn_cmdline_printf(pool, _("\nRecovery completed.\n")));
1324 /* Since db transactions may have been replayed, it's nice to tell
1325 people what the latest revision is. It also proves that the
1326 recovery actually worked. */
1327 SVN_ERR(open_repos(&repos, opt_state->repository_path, pool));
1328 SVN_ERR(svn_fs_youngest_rev(&youngest_rev, svn_repos_fs(repos), pool));
1329 SVN_ERR(svn_cmdline_printf(pool, _("The latest repos revision is %ld.\n"),
1332 return SVN_NO_ERROR;
1336 /* This implements `svn_opt_subcommand_t'. */
1337 static svn_error_t *
1338 list_dblogs(apr_getopt_t *os, void *baton, svn_boolean_t only_unused,
1341 struct svnadmin_opt_state *opt_state = baton;
1342 apr_array_header_t *logfiles;
1345 /* Expect no more arguments. */
1346 SVN_ERR(parse_args(NULL, os, 0, 0, pool));
1348 SVN_ERR(svn_repos_db_logfiles(&logfiles,
1349 opt_state->repository_path,
1353 /* Loop, printing log files. We append the log paths to the
1354 repository path, making sure to return everything to the native
1355 style before printing. */
1356 for (i = 0; i < logfiles->nelts; i++)
1358 const char *log_utf8;
1359 log_utf8 = svn_dirent_join(opt_state->repository_path,
1360 APR_ARRAY_IDX(logfiles, i, const char *),
1362 log_utf8 = svn_dirent_local_style(log_utf8, pool);
1363 SVN_ERR(svn_cmdline_printf(pool, "%s\n", log_utf8));
1366 return SVN_NO_ERROR;
1370 /* This implements `svn_opt_subcommand_t'. */
1371 static svn_error_t *
1372 subcommand_list_dblogs(apr_getopt_t *os, void *baton, apr_pool_t *pool)
1374 SVN_ERR(list_dblogs(os, baton, FALSE, pool));
1375 return SVN_NO_ERROR;
1379 /* This implements `svn_opt_subcommand_t'. */
1380 static svn_error_t *
1381 subcommand_list_unused_dblogs(apr_getopt_t *os, void *baton, apr_pool_t *pool)
1383 /* Expect no more arguments. */
1384 SVN_ERR(parse_args(NULL, os, 0, 0, pool));
1386 SVN_ERR(list_dblogs(os, baton, TRUE, pool));
1387 return SVN_NO_ERROR;
1391 /* This implements `svn_opt_subcommand_t'. */
1392 static svn_error_t *
1393 subcommand_rmtxns(apr_getopt_t *os, void *baton, apr_pool_t *pool)
1395 struct svnadmin_opt_state *opt_state = baton;
1399 apr_array_header_t *args;
1401 apr_pool_t *subpool = svn_pool_create(pool);
1403 SVN_ERR(svn_opt_parse_all_args(&args, os, pool));
1405 SVN_ERR(open_repos(&repos, opt_state->repository_path, pool));
1406 fs = svn_repos_fs(repos);
1408 /* All the rest of the arguments are transaction names. */
1409 for (i = 0; i < args->nelts; i++)
1411 const char *txn_name = APR_ARRAY_IDX(args, i, const char *);
1412 const char *txn_name_utf8;
1415 svn_pool_clear(subpool);
1417 SVN_ERR(svn_utf_cstring_to_utf8(&txn_name_utf8, txn_name, subpool));
1419 /* Try to open the txn. If that succeeds, try to abort it. */
1420 err = svn_fs_open_txn(&txn, fs, txn_name_utf8, subpool);
1422 err = svn_fs_abort_txn(txn, subpool);
1424 /* If either the open or the abort of the txn fails because that
1425 transaction is dead, just try to purge the thing. Else,
1426 there was either an error worth reporting, or not error at
1428 if (err && (err->apr_err == SVN_ERR_FS_TRANSACTION_DEAD))
1430 svn_error_clear(err);
1431 err = svn_fs_purge_txn(fs, txn_name_utf8, subpool);
1434 /* If we had a real from the txn open, abort, or purge, we clear
1435 that error and just report to the user that we had an issue
1436 with this particular txn. */
1439 svn_handle_error2(err, stderr, FALSE /* non-fatal */, "svnadmin: ");
1440 svn_error_clear(err);
1442 else if (! opt_state->quiet)
1444 SVN_ERR(svn_cmdline_printf(subpool, _("Transaction '%s' removed.\n"),
1449 svn_pool_destroy(subpool);
1451 return SVN_NO_ERROR;
1455 /* A helper for the 'setrevprop' and 'setlog' commands. Expects
1456 OPT_STATE->use_pre_revprop_change_hook and
1457 OPT_STATE->use_post_revprop_change_hook to be set appropriately. */
1458 static svn_error_t *
1459 set_revprop(const char *prop_name, const char *filename,
1460 struct svnadmin_opt_state *opt_state, apr_pool_t *pool)
1463 svn_string_t *prop_value = svn_string_create_empty(pool);
1464 svn_stringbuf_t *file_contents;
1466 SVN_ERR(svn_stringbuf_from_file2(&file_contents, filename, pool));
1468 prop_value->data = file_contents->data;
1469 prop_value->len = file_contents->len;
1471 SVN_ERR(svn_subst_translate_string2(&prop_value, NULL, NULL, prop_value,
1472 NULL, FALSE, pool, pool));
1474 /* Open the filesystem */
1475 SVN_ERR(open_repos(&repos, opt_state->repository_path, pool));
1477 /* If we are bypassing the hooks system, we just hit the filesystem
1479 SVN_ERR(svn_repos_fs_change_rev_prop4(
1480 repos, opt_state->start_revision.value.number,
1481 NULL, prop_name, NULL, prop_value,
1482 opt_state->use_pre_revprop_change_hook,
1483 opt_state->use_post_revprop_change_hook,
1486 return SVN_NO_ERROR;
1490 /* This implements `svn_opt_subcommand_t'. */
1491 static svn_error_t *
1492 subcommand_setrevprop(apr_getopt_t *os, void *baton, apr_pool_t *pool)
1494 struct svnadmin_opt_state *opt_state = baton;
1495 apr_array_header_t *args;
1496 const char *prop_name, *filename;
1498 /* Expect two more arguments: NAME FILE */
1499 SVN_ERR(parse_args(&args, os, 2, 2, pool));
1500 prop_name = APR_ARRAY_IDX(args, 0, const char *);
1501 filename = APR_ARRAY_IDX(args, 1, const char *);
1502 SVN_ERR(target_arg_to_dirent(&filename, filename, pool));
1504 if (opt_state->start_revision.kind != svn_opt_revision_number)
1505 return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
1506 _("Missing revision"));
1507 else if (opt_state->end_revision.kind != svn_opt_revision_unspecified)
1508 return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
1509 _("Only one revision allowed"));
1511 return set_revprop(prop_name, filename, opt_state, pool);
1515 /* This implements `svn_opt_subcommand_t'. */
1516 static svn_error_t *
1517 subcommand_setuuid(apr_getopt_t *os, void *baton, apr_pool_t *pool)
1519 struct svnadmin_opt_state *opt_state = baton;
1520 apr_array_header_t *args;
1523 const char *uuid = NULL;
1525 /* Expect zero or one more arguments: [UUID] */
1526 SVN_ERR(parse_args(&args, os, 0, 1, pool));
1527 if (args->nelts == 1)
1528 uuid = APR_ARRAY_IDX(args, 0, const char *);
1530 SVN_ERR(open_repos(&repos, opt_state->repository_path, pool));
1531 fs = svn_repos_fs(repos);
1532 return svn_fs_set_uuid(fs, uuid, pool);
1536 /* This implements `svn_opt_subcommand_t'. */
1537 static svn_error_t *
1538 subcommand_setlog(apr_getopt_t *os, void *baton, apr_pool_t *pool)
1540 struct svnadmin_opt_state *opt_state = baton;
1541 apr_array_header_t *args;
1542 const char *filename;
1544 /* Expect one more argument: FILE */
1545 SVN_ERR(parse_args(&args, os, 1, 1, pool));
1546 filename = APR_ARRAY_IDX(args, 0, const char *);
1547 SVN_ERR(target_arg_to_dirent(&filename, filename, pool));
1549 if (opt_state->start_revision.kind != svn_opt_revision_number)
1550 return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
1551 _("Missing revision"));
1552 else if (opt_state->end_revision.kind != svn_opt_revision_unspecified)
1553 return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
1554 _("Only one revision allowed"));
1556 /* set_revprop() responds only to pre-/post-revprop-change opts. */
1557 if (!opt_state->bypass_hooks)
1559 opt_state->use_pre_revprop_change_hook = TRUE;
1560 opt_state->use_post_revprop_change_hook = TRUE;
1563 return set_revprop(SVN_PROP_REVISION_LOG, filename, opt_state, pool);
1567 /* This implements 'svn_opt_subcommand_t'. */
1568 static svn_error_t *
1569 subcommand_pack(apr_getopt_t *os, void *baton, apr_pool_t *pool)
1571 struct svnadmin_opt_state *opt_state = baton;
1573 svn_stream_t *progress_stream = NULL;
1575 /* Expect no more arguments. */
1576 SVN_ERR(parse_args(NULL, os, 0, 0, pool));
1578 SVN_ERR(open_repos(&repos, opt_state->repository_path, pool));
1580 /* Progress feedback goes to STDOUT, unless they asked to suppress it. */
1581 if (! opt_state->quiet)
1582 progress_stream = recode_stream_create(stdout, pool);
1584 return svn_error_trace(
1585 svn_repos_fs_pack2(repos, !opt_state->quiet ? repos_notify_handler : NULL,
1586 progress_stream, check_cancel, NULL, pool));
1590 /* This implements `svn_opt_subcommand_t'. */
1591 static svn_error_t *
1592 subcommand_verify(apr_getopt_t *os, void *baton, apr_pool_t *pool)
1594 struct svnadmin_opt_state *opt_state = baton;
1597 svn_revnum_t youngest, lower, upper;
1598 svn_stream_t *progress_stream = NULL;
1600 /* Expect no more arguments. */
1601 SVN_ERR(parse_args(NULL, os, 0, 0, pool));
1603 if (opt_state->txn_id
1604 && (opt_state->start_revision.kind != svn_opt_revision_unspecified
1605 || opt_state->end_revision.kind != svn_opt_revision_unspecified))
1607 return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
1608 _("--revision (-r) and --transaction (-t) "
1609 "are mutually exclusive"));
1612 SVN_ERR(open_repos(&repos, opt_state->repository_path, pool));
1613 fs = svn_repos_fs(repos);
1614 SVN_ERR(svn_fs_youngest_rev(&youngest, fs, pool));
1617 if (opt_state->txn_id)
1620 svn_fs_root_t *root;
1622 SVN_ERR(svn_fs_open_txn(&txn, fs, opt_state->txn_id, pool));
1623 SVN_ERR(svn_fs_txn_root(&root, txn, pool));
1624 SVN_ERR(svn_fs_verify_root(root, pool));
1625 return SVN_NO_ERROR;
1631 /* Find the revision numbers at which to start and end. */
1632 SVN_ERR(get_revnum(&lower, &opt_state->start_revision,
1633 youngest, repos, pool));
1634 SVN_ERR(get_revnum(&upper, &opt_state->end_revision,
1635 youngest, repos, pool));
1637 if (upper == SVN_INVALID_REVNUM)
1642 if (! opt_state->quiet)
1643 progress_stream = recode_stream_create(stderr, pool);
1645 return svn_repos_verify_fs2(repos, lower, upper,
1647 ? repos_notify_handler : NULL,
1648 progress_stream, check_cancel, NULL, pool);
1651 /* This implements `svn_opt_subcommand_t'. */
1653 subcommand_hotcopy(apr_getopt_t *os, void *baton, apr_pool_t *pool)
1655 struct svnadmin_opt_state *opt_state = baton;
1656 apr_array_header_t *targets;
1657 const char *new_repos_path;
1659 /* Expect one more argument: NEW_REPOS_PATH */
1660 SVN_ERR(parse_args(&targets, os, 1, 1, pool));
1661 new_repos_path = APR_ARRAY_IDX(targets, 0, const char *);
1662 SVN_ERR(target_arg_to_dirent(&new_repos_path, new_repos_path, pool));
1664 return svn_repos_hotcopy2(opt_state->repository_path, new_repos_path,
1665 opt_state->clean_logs, opt_state->incremental,
1666 check_cancel, NULL, pool);
1669 /* This implements `svn_opt_subcommand_t'. */
1670 static svn_error_t *
1671 subcommand_lock(apr_getopt_t *os, void *baton, apr_pool_t *pool)
1673 struct svnadmin_opt_state *opt_state = baton;
1676 svn_fs_access_t *access;
1677 apr_array_header_t *args;
1678 const char *username;
1679 const char *lock_path;
1680 const char *comment_file_name;
1681 svn_stringbuf_t *file_contents;
1682 const char *lock_path_utf8;
1684 const char *lock_token = NULL;
1686 /* Expect three more arguments: PATH USERNAME COMMENT-FILE */
1687 SVN_ERR(parse_args(&args, os, 3, 4, pool));
1688 lock_path = APR_ARRAY_IDX(args, 0, const char *);
1689 username = APR_ARRAY_IDX(args, 1, const char *);
1690 comment_file_name = APR_ARRAY_IDX(args, 2, const char *);
1692 /* Expect one more optional argument: TOKEN */
1693 if (args->nelts == 4)
1694 lock_token = APR_ARRAY_IDX(args, 3, const char *);
1696 SVN_ERR(target_arg_to_dirent(&comment_file_name, comment_file_name, pool));
1698 SVN_ERR(open_repos(&repos, opt_state->repository_path, pool));
1699 fs = svn_repos_fs(repos);
1701 /* Create an access context describing the user. */
1702 SVN_ERR(svn_fs_create_access(&access, username, pool));
1704 /* Attach the access context to the filesystem. */
1705 SVN_ERR(svn_fs_set_access(fs, access));
1707 SVN_ERR(svn_stringbuf_from_file2(&file_contents, comment_file_name, pool));
1709 SVN_ERR(svn_utf_cstring_to_utf8(&lock_path_utf8, lock_path, pool));
1711 if (opt_state->bypass_hooks)
1712 SVN_ERR(svn_fs_lock(&lock, fs, lock_path_utf8,
1714 file_contents->data, /* comment */
1715 0, /* is_dav_comment */
1716 0, /* no expiration time. */
1720 SVN_ERR(svn_repos_fs_lock(&lock, repos, lock_path_utf8,
1722 file_contents->data, /* comment */
1723 0, /* is_dav_comment */
1724 0, /* no expiration time. */
1728 SVN_ERR(svn_cmdline_printf(pool, _("'%s' locked by user '%s'.\n"),
1729 lock_path, username));
1730 return SVN_NO_ERROR;
1733 static svn_error_t *
1734 subcommand_lslocks(apr_getopt_t *os, void *baton, apr_pool_t *pool)
1736 struct svnadmin_opt_state *opt_state = baton;
1737 apr_array_header_t *targets;
1739 const char *fs_path = "/";
1741 apr_hash_index_t *hi;
1743 SVN_ERR(svn_opt__args_to_target_array(&targets, os,
1744 apr_array_make(pool, 0,
1745 sizeof(const char *)),
1747 if (targets->nelts > 1)
1748 return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, 0,
1749 _("Too many arguments given"));
1751 fs_path = APR_ARRAY_IDX(targets, 0, const char *);
1753 SVN_ERR(open_repos(&repos, opt_state->repository_path, pool));
1755 /* Fetch all locks on or below the root directory. */
1756 SVN_ERR(svn_repos_fs_get_locks2(&locks, repos, fs_path, svn_depth_infinity,
1759 for (hi = apr_hash_first(pool, locks); hi; hi = apr_hash_next(hi))
1761 const char *cr_date, *exp_date = "";
1762 const char *path = svn__apr_hash_index_key(hi);
1763 svn_lock_t *lock = svn__apr_hash_index_val(hi);
1764 int comment_lines = 0;
1766 cr_date = svn_time_to_human_cstring(lock->creation_date, pool);
1768 if (lock->expiration_date)
1769 exp_date = svn_time_to_human_cstring(lock->expiration_date, pool);
1772 comment_lines = svn_cstring_count_newlines(lock->comment) + 1;
1774 SVN_ERR(svn_cmdline_printf(pool, _("Path: %s\n"), path));
1775 SVN_ERR(svn_cmdline_printf(pool, _("UUID Token: %s\n"), lock->token));
1776 SVN_ERR(svn_cmdline_printf(pool, _("Owner: %s\n"), lock->owner));
1777 SVN_ERR(svn_cmdline_printf(pool, _("Created: %s\n"), cr_date));
1778 SVN_ERR(svn_cmdline_printf(pool, _("Expires: %s\n"), exp_date));
1779 SVN_ERR(svn_cmdline_printf(pool,
1780 Q_("Comment (%i line):\n%s\n\n",
1781 "Comment (%i lines):\n%s\n\n",
1784 lock->comment ? lock->comment : ""));
1787 return SVN_NO_ERROR;
1792 static svn_error_t *
1793 subcommand_rmlocks(apr_getopt_t *os, void *baton, apr_pool_t *pool)
1795 struct svnadmin_opt_state *opt_state = baton;
1798 svn_fs_access_t *access;
1800 apr_array_header_t *args;
1802 const char *username;
1803 apr_pool_t *subpool = svn_pool_create(pool);
1805 SVN_ERR(open_repos(&repos, opt_state->repository_path, pool));
1806 fs = svn_repos_fs(repos);
1808 /* svn_fs_unlock() demands that some username be associated with the
1809 filesystem, so just use the UID of the person running 'svnadmin'.*/
1810 username = svn_user_get_name(pool);
1812 username = "administrator";
1814 /* Create an access context describing the current user. */
1815 SVN_ERR(svn_fs_create_access(&access, username, pool));
1817 /* Attach the access context to the filesystem. */
1818 SVN_ERR(svn_fs_set_access(fs, access));
1820 /* Parse out any options. */
1821 SVN_ERR(svn_opt_parse_all_args(&args, os, pool));
1823 /* Our usage requires at least one FS path. */
1824 if (args->nelts == 0)
1825 return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, 0,
1826 _("No paths to unlock provided"));
1828 /* All the rest of the arguments are paths from which to remove locks. */
1829 for (i = 0; i < args->nelts; i++)
1831 const char *lock_path = APR_ARRAY_IDX(args, i, const char *);
1832 const char *lock_path_utf8;
1835 SVN_ERR(svn_utf_cstring_to_utf8(&lock_path_utf8, lock_path, subpool));
1837 /* Fetch the path's svn_lock_t. */
1838 err = svn_fs_get_lock(&lock, fs, lock_path_utf8, subpool);
1843 SVN_ERR(svn_cmdline_printf(subpool,
1844 _("Path '%s' isn't locked.\n"),
1849 /* Now forcibly destroy the lock. */
1850 err = svn_fs_unlock(fs, lock_path_utf8,
1851 lock->token, 1 /* force */, subpool);
1855 SVN_ERR(svn_cmdline_printf(subpool,
1856 _("Removed lock on '%s'.\n"), lock->path));
1861 /* Print the error, but move on to the next lock. */
1862 svn_handle_error2(err, stderr, FALSE /* non-fatal */, "svnadmin: ");
1863 svn_error_clear(err);
1866 svn_pool_clear(subpool);
1869 svn_pool_destroy(subpool);
1870 return SVN_NO_ERROR;
1874 /* This implements `svn_opt_subcommand_t'. */
1875 static svn_error_t *
1876 subcommand_unlock(apr_getopt_t *os, void *baton, apr_pool_t *pool)
1878 struct svnadmin_opt_state *opt_state = baton;
1881 svn_fs_access_t *access;
1882 apr_array_header_t *args;
1883 const char *username;
1884 const char *lock_path;
1885 const char *lock_path_utf8;
1886 const char *lock_token = NULL;
1888 /* Expect three more arguments: PATH USERNAME TOKEN */
1889 SVN_ERR(parse_args(&args, os, 3, 3, pool));
1890 lock_path = APR_ARRAY_IDX(args, 0, const char *);
1891 username = APR_ARRAY_IDX(args, 1, const char *);
1892 lock_token = APR_ARRAY_IDX(args, 2, const char *);
1894 /* Open the repos/FS, and associate an access context containing
1896 SVN_ERR(open_repos(&repos, opt_state->repository_path, pool));
1897 fs = svn_repos_fs(repos);
1898 SVN_ERR(svn_fs_create_access(&access, username, pool));
1899 SVN_ERR(svn_fs_set_access(fs, access));
1901 SVN_ERR(svn_utf_cstring_to_utf8(&lock_path_utf8, lock_path, pool));
1902 if (opt_state->bypass_hooks)
1903 SVN_ERR(svn_fs_unlock(fs, lock_path_utf8, lock_token,
1906 SVN_ERR(svn_repos_fs_unlock(repos, lock_path_utf8, lock_token,
1909 SVN_ERR(svn_cmdline_printf(pool, _("'%s' unlocked by user '%s'.\n"),
1910 lock_path, username));
1911 return SVN_NO_ERROR;
1915 /* This implements `svn_opt_subcommand_t'. */
1916 static svn_error_t *
1917 subcommand_upgrade(apr_getopt_t *os, void *baton, apr_pool_t *pool)
1920 struct svnadmin_opt_state *opt_state = baton;
1921 svn_stream_t *stdout_stream;
1923 /* Expect no more arguments. */
1924 SVN_ERR(parse_args(NULL, os, 0, 0, pool));
1926 SVN_ERR(svn_stream_for_stdout(&stdout_stream, pool));
1928 /* Restore default signal handlers. */
1929 setup_cancellation_signals(SIG_DFL);
1931 err = svn_repos_upgrade2(opt_state->repository_path, TRUE,
1932 repos_notify_handler, stdout_stream, pool);
1935 if (APR_STATUS_IS_EAGAIN(err->apr_err))
1937 svn_error_clear(err);
1939 if (! opt_state->wait)
1940 return svn_error_create(SVN_ERR_REPOS_LOCKED, NULL,
1941 _("Failed to get exclusive repository "
1942 "access; perhaps another process\n"
1943 "such as httpd, svnserve or svn "
1945 SVN_ERR(svn_cmdline_printf(pool,
1946 _("Waiting on repository lock; perhaps"
1947 " another process has it open?\n")));
1948 SVN_ERR(svn_cmdline_fflush(stdout));
1949 SVN_ERR(svn_repos_upgrade2(opt_state->repository_path, FALSE,
1950 repos_notify_handler, stdout_stream,
1953 else if (err->apr_err == SVN_ERR_FS_UNSUPPORTED_UPGRADE)
1955 return svn_error_quick_wrap(err,
1956 _("Upgrade of this repository's underlying versioned "
1957 "filesystem is not supported; consider "
1958 "dumping and loading the data elsewhere"));
1960 else if (err->apr_err == SVN_ERR_REPOS_UNSUPPORTED_UPGRADE)
1962 return svn_error_quick_wrap(err,
1963 _("Upgrade of this repository is not supported; consider "
1964 "dumping and loading the data elsewhere"));
1969 SVN_ERR(svn_cmdline_printf(pool, _("\nUpgrade completed.\n")));
1970 return SVN_NO_ERROR;
1977 /* Report and clear the error ERR, and return EXIT_FAILURE. */
1978 #define EXIT_ERROR(err) \
1979 svn_cmdline_handle_exit_error(err, NULL, "svnadmin: ")
1981 /* A redefinition of the public SVN_INT_ERR macro, that suppresses the
1982 * error message if it is SVN_ERR_IO_PIPE_WRITE_ERROR, amd with the
1983 * program name 'svnadmin' instead of 'svn'. */
1985 #define SVN_INT_ERR(expr) \
1987 svn_error_t *svn_err__temp = (expr); \
1988 if (svn_err__temp) \
1989 return EXIT_ERROR(svn_err__temp); \
1993 sub_main(int argc, const char *argv[], apr_pool_t *pool)
1996 apr_status_t apr_err;
1998 const svn_opt_subcommand_desc2_t *subcommand = NULL;
1999 struct svnadmin_opt_state opt_state = { 0 };
2002 apr_array_header_t *received_opts;
2004 svn_boolean_t dash_F_arg = FALSE;
2006 received_opts = apr_array_make(pool, SVN_OPT_MAX_OPTIONS, sizeof(int));
2008 /* Check library versions */
2009 SVN_INT_ERR(check_lib_versions());
2011 /* Initialize the FS library. */
2012 SVN_INT_ERR(svn_fs_initialize(pool));
2016 SVN_INT_ERR(subcommand_help(NULL, NULL, pool));
2017 return EXIT_FAILURE;
2020 /* Initialize opt_state. */
2021 opt_state.start_revision.kind = svn_opt_revision_unspecified;
2022 opt_state.end_revision.kind = svn_opt_revision_unspecified;
2023 opt_state.memory_cache_size = svn_cache_config_get()->cache_size;
2025 /* Parse options. */
2026 SVN_INT_ERR(svn_cmdline__getopt_init(&os, argc, argv, pool));
2032 const char *opt_arg;
2033 const char *utf8_opt_arg;
2035 /* Parse the next option. */
2036 apr_err = apr_getopt_long(os, options_table, &opt_id, &opt_arg);
2037 if (APR_STATUS_IS_EOF(apr_err))
2041 SVN_INT_ERR(subcommand_help(NULL, NULL, pool));
2042 return EXIT_FAILURE;
2045 /* Stash the option code in an array before parsing it. */
2046 APR_ARRAY_PUSH(received_opts, int) = opt_id;
2051 if (opt_state.start_revision.kind != svn_opt_revision_unspecified)
2053 err = svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
2054 _("Multiple revision arguments encountered; "
2055 "try '-r N:M' instead of '-r N -r M'"));
2056 return EXIT_ERROR(err);
2058 if (svn_opt_parse_revision(&(opt_state.start_revision),
2059 &(opt_state.end_revision),
2060 opt_arg, pool) != 0)
2062 err = svn_utf_cstring_to_utf8(&utf8_opt_arg, opt_arg,
2066 err = svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
2067 _("Syntax error in revision argument '%s'"),
2069 return EXIT_ERROR(err);
2074 opt_state.txn_id = opt_arg;
2078 opt_state.quiet = TRUE;
2082 opt_state.help = TRUE;
2085 opt_state.memory_cache_size
2086 = 0x100000 * apr_strtoi64(opt_arg, NULL, 0);
2089 SVN_INT_ERR(svn_utf_cstring_to_utf8(&utf8_opt_arg, opt_arg, pool));
2090 SVN_INT_ERR(svn_stringbuf_from_file2(&(opt_state.filedata),
2091 utf8_opt_arg, pool));
2093 case svnadmin__version:
2094 opt_state.version = TRUE;
2096 case svnadmin__incremental:
2097 opt_state.incremental = TRUE;
2099 case svnadmin__deltas:
2100 opt_state.use_deltas = TRUE;
2102 case svnadmin__ignore_uuid:
2103 opt_state.uuid_action = svn_repos_load_uuid_ignore;
2105 case svnadmin__force_uuid:
2106 opt_state.uuid_action = svn_repos_load_uuid_force;
2108 case svnadmin__pre_1_4_compatible:
2109 opt_state.pre_1_4_compatible = TRUE;
2111 case svnadmin__pre_1_5_compatible:
2112 opt_state.pre_1_5_compatible = TRUE;
2114 case svnadmin__pre_1_6_compatible:
2115 opt_state.pre_1_6_compatible = TRUE;
2117 case svnadmin__compatible_version:
2119 svn_version_t latest = { SVN_VER_MAJOR, SVN_VER_MINOR,
2120 SVN_VER_PATCH, NULL };
2121 svn_version_t *compatible_version;
2123 /* Parse the version string which carries our target
2125 SVN_INT_ERR(svn_version__parse_version_string(&compatible_version,
2128 /* We can't create repository with a version older than 1.0.0. */
2129 if (! svn_version__at_least(compatible_version, 1, 0, 0))
2131 err = svn_error_createf(SVN_ERR_UNSUPPORTED_FEATURE, NULL,
2132 _("Cannot create pre-1.0-compatible "
2134 return EXIT_ERROR(err);
2137 /* We can't create repository with a version newer than what
2138 the running version of Subversion supports. */
2139 if (! svn_version__at_least(&latest,
2140 compatible_version->major,
2141 compatible_version->minor,
2142 compatible_version->patch))
2144 err = svn_error_createf(SVN_ERR_UNSUPPORTED_FEATURE, NULL,
2145 _("Cannot guarantee compatibility "
2146 "beyond the current running version "
2149 return EXIT_ERROR(err);
2152 opt_state.compatible_version = compatible_version;
2155 case svnadmin__fs_type:
2156 SVN_INT_ERR(svn_utf_cstring_to_utf8(&opt_state.fs_type, opt_arg, pool));
2158 case svnadmin__parent_dir:
2159 SVN_INT_ERR(svn_utf_cstring_to_utf8(&opt_state.parent_dir, opt_arg,
2161 opt_state.parent_dir
2162 = svn_dirent_internal_style(opt_state.parent_dir, pool);
2164 case svnadmin__use_pre_commit_hook:
2165 opt_state.use_pre_commit_hook = TRUE;
2167 case svnadmin__use_post_commit_hook:
2168 opt_state.use_post_commit_hook = TRUE;
2170 case svnadmin__use_pre_revprop_change_hook:
2171 opt_state.use_pre_revprop_change_hook = TRUE;
2173 case svnadmin__use_post_revprop_change_hook:
2174 opt_state.use_post_revprop_change_hook = TRUE;
2176 case svnadmin__bdb_txn_nosync:
2177 opt_state.bdb_txn_nosync = TRUE;
2179 case svnadmin__bdb_log_keep:
2180 opt_state.bdb_log_keep = TRUE;
2182 case svnadmin__bypass_hooks:
2183 opt_state.bypass_hooks = TRUE;
2185 case svnadmin__bypass_prop_validation:
2186 opt_state.bypass_prop_validation = TRUE;
2188 case svnadmin__clean_logs:
2189 opt_state.clean_logs = TRUE;
2191 case svnadmin__config_dir:
2192 SVN_INT_ERR(svn_utf_cstring_to_utf8(&utf8_opt_arg, opt_arg, pool));
2193 opt_state.config_dir =
2194 apr_pstrdup(pool, svn_dirent_canonicalize(utf8_opt_arg, pool));
2196 case svnadmin__wait:
2197 opt_state.wait = TRUE;
2201 SVN_INT_ERR(subcommand_help(NULL, NULL, pool));
2202 return EXIT_FAILURE;
2204 } /* close `switch' */
2205 } /* close `while' */
2207 /* If the user asked for help, then the rest of the arguments are
2208 the names of subcommands to get help on (if any), or else they're
2209 just typos/mistakes. Whatever the case, the subcommand to
2210 actually run is subcommand_help(). */
2212 subcommand = svn_opt_get_canonical_subcommand2(cmd_table, "help");
2214 /* If we're not running the `help' subcommand, then look for a
2215 subcommand in the first argument. */
2216 if (subcommand == NULL)
2218 if (os->ind >= os->argc)
2220 if (opt_state.version)
2222 /* Use the "help" subcommand to handle the "--version" option. */
2223 static const svn_opt_subcommand_desc2_t pseudo_cmd =
2224 { "--version", subcommand_help, {0}, "",
2225 {svnadmin__version, /* must accept its own option */
2229 subcommand = &pseudo_cmd;
2233 svn_error_clear(svn_cmdline_fprintf(stderr, pool,
2234 _("subcommand argument required\n")));
2235 SVN_INT_ERR(subcommand_help(NULL, NULL, pool));
2236 return EXIT_FAILURE;
2241 const char *first_arg = os->argv[os->ind++];
2242 subcommand = svn_opt_get_canonical_subcommand2(cmd_table, first_arg);
2243 if (subcommand == NULL)
2245 const char *first_arg_utf8;
2246 SVN_INT_ERR(svn_utf_cstring_to_utf8(&first_arg_utf8,
2249 svn_cmdline_fprintf(stderr, pool,
2250 _("Unknown subcommand: '%s'\n"),
2252 SVN_INT_ERR(subcommand_help(NULL, NULL, pool));
2253 return EXIT_FAILURE;
2258 /* Every subcommand except `help' and `freeze' with '-F' require a
2259 second argument -- the repository path. Parse it out here and
2260 store it in opt_state. */
2261 if (!(subcommand->cmd_func == subcommand_help
2262 || (subcommand->cmd_func == subcommand_freeze && dash_F_arg)))
2264 const char *repos_path = NULL;
2266 if (os->ind >= os->argc)
2268 err = svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
2269 _("Repository argument required"));
2270 return EXIT_ERROR(err);
2273 if ((err = svn_utf_cstring_to_utf8(&repos_path,
2274 os->argv[os->ind++], pool)))
2276 return EXIT_ERROR(err);
2279 if (svn_path_is_url(repos_path))
2281 err = svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
2282 _("'%s' is a URL when it should be a "
2283 "local path"), repos_path);
2284 return EXIT_ERROR(err);
2287 opt_state.repository_path = svn_dirent_internal_style(repos_path, pool);
2290 /* Check that the subcommand wasn't passed any inappropriate options. */
2291 for (i = 0; i < received_opts->nelts; i++)
2293 opt_id = APR_ARRAY_IDX(received_opts, i, int);
2295 /* All commands implicitly accept --help, so just skip over this
2296 when we see it. Note that we don't want to include this option
2297 in their "accepted options" list because it would be awfully
2298 redundant to display it in every commands' help text. */
2299 if (opt_id == 'h' || opt_id == '?')
2302 if (! svn_opt_subcommand_takes_option3(subcommand, opt_id, NULL))
2305 const apr_getopt_option_t *badopt =
2306 svn_opt_get_option_from_code2(opt_id, options_table, subcommand,
2308 svn_opt_format_option(&optstr, badopt, FALSE, pool);
2309 if (subcommand->name[0] == '-')
2310 SVN_INT_ERR(subcommand_help(NULL, NULL, pool));
2312 svn_error_clear(svn_cmdline_fprintf(stderr, pool
2313 , _("Subcommand '%s' doesn't accept option '%s'\n"
2314 "Type 'svnadmin help %s' for usage.\n"),
2315 subcommand->name, optstr, subcommand->name));
2316 return EXIT_FAILURE;
2320 /* Set up our cancellation support. */
2321 setup_cancellation_signals(signal_handler);
2324 /* Disable SIGPIPE generation for the platforms that have it. */
2325 apr_signal(SIGPIPE, SIG_IGN);
2329 /* Disable SIGXFSZ generation for the platforms that have it, otherwise
2330 * working with large files when compiled against an APR that doesn't have
2331 * large file support will crash the program, which is uncool. */
2332 apr_signal(SIGXFSZ, SIG_IGN);
2335 /* Configure FSFS caches for maximum efficiency with svnadmin.
2336 * Also, apply the respective command line parameters, if given. */
2338 svn_cache_config_t settings = *svn_cache_config_get();
2340 settings.cache_size = opt_state.memory_cache_size;
2341 settings.single_threaded = TRUE;
2343 svn_cache_config_set(&settings);
2346 /* Run the subcommand. */
2347 err = (*subcommand->cmd_func)(os, &opt_state, pool);
2350 /* For argument-related problems, suggest using the 'help'
2352 if (err->apr_err == SVN_ERR_CL_INSUFFICIENT_ARGS
2353 || err->apr_err == SVN_ERR_CL_ARG_PARSING_ERROR)
2355 err = svn_error_quick_wrap(err,
2356 _("Try 'svnadmin help' for more info"));
2358 return EXIT_ERROR(err);
2362 /* Ensure that everything is written to stdout, so the user will
2363 see any print errors. */
2364 err = svn_cmdline_fflush(stdout);
2367 return EXIT_ERROR(err);
2369 return EXIT_SUCCESS;
2374 main(int argc, const char *argv[])
2379 /* Initialize the app. */
2380 if (svn_cmdline_init("svnadmin", stderr) != EXIT_SUCCESS)
2381 return EXIT_FAILURE;
2383 /* Create our top-level pool. Use a separate mutexless allocator,
2384 * given this application is single threaded.
2386 pool = apr_allocator_owner_get(svn_pool_create_allocator(FALSE));
2388 exit_code = sub_main(argc, argv, pool);
2390 svn_pool_destroy(pool);