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"
42 #include "svn_sorts.h"
47 #include "private/svn_cmdline_private.h"
48 #include "private/svn_opt_private.h"
49 #include "private/svn_sorts_private.h"
50 #include "private/svn_subr_private.h"
52 #include "svn_private_config.h"
57 /* FSFS format 7's "block-read" feature performs poorly with small caches.
58 * Enable it only if caches above this threshold have been configured.
59 * The current threshold is 64MB. */
60 #define BLOCK_READ_CACHE_THRESHOLD (0x40 * 0x100000)
62 /* A flag to see if we've been cancelled by the client or not. */
63 static volatile sig_atomic_t cancelled = FALSE;
65 /* A signal handler to support cancellation. */
67 signal_handler(int signum)
69 apr_signal(signum, SIG_IGN);
74 /* A helper to set up the cancellation signal handlers. */
76 setup_cancellation_signals(void (*handler)(int signum))
78 apr_signal(SIGINT, handler);
80 /* SIGBREAK is a Win32 specific signal generated by ctrl-break. */
81 apr_signal(SIGBREAK, handler);
84 apr_signal(SIGHUP, handler);
87 apr_signal(SIGTERM, handler);
92 /* Our cancellation callback. */
94 check_cancel(void *baton)
97 return svn_error_create(SVN_ERR_CANCELLED, NULL, _("Caught signal"));
103 /* Custom filesystem warning function. */
105 warning_func(void *baton,
110 svn_handle_warning2(stderr, err, "svnadmin: ");
114 /* Helper to open a repository and set a warning func (so we don't
115 * SEGFAULT when libsvn_fs's default handler gets run). */
117 open_repos(svn_repos_t **repos,
121 /* Enable the "block-read" feature (where it applies)? */
122 svn_boolean_t use_block_read
123 = svn_cache_config_get()->cache_size > BLOCK_READ_CACHE_THRESHOLD;
125 /* construct FS configuration parameters: enable caches for r/o data */
126 apr_hash_t *fs_config = apr_hash_make(pool);
127 svn_hash_sets(fs_config, SVN_FS_CONFIG_FSFS_CACHE_DELTAS, "1");
128 svn_hash_sets(fs_config, SVN_FS_CONFIG_FSFS_CACHE_FULLTEXTS, "1");
129 svn_hash_sets(fs_config, SVN_FS_CONFIG_FSFS_CACHE_REVPROPS, "2");
130 svn_hash_sets(fs_config, SVN_FS_CONFIG_FSFS_CACHE_NS,
131 svn_uuid_generate(pool));
132 svn_hash_sets(fs_config, SVN_FS_CONFIG_FSFS_BLOCK_READ,
133 use_block_read ? "1" : "0");
135 /* now, open the requested repository */
136 SVN_ERR(svn_repos_open3(repos, path, fs_config, pool, pool));
137 svn_fs_set_warning_func(svn_repos_fs(*repos), warning_func, NULL);
142 /* Version compatibility check */
144 check_lib_versions(void)
146 static const svn_version_checklist_t checklist[] =
148 { "svn_subr", svn_subr_version },
149 { "svn_repos", svn_repos_version },
150 { "svn_fs", svn_fs_version },
151 { "svn_delta", svn_delta_version },
154 SVN_VERSION_DEFINE(my_version);
156 return svn_ver_check_list2(&my_version, checklist, svn_ver_equal);
163 static svn_opt_subcommand_t
164 subcommand_crashtest,
166 subcommand_delrevprop,
174 subcommand_list_dblogs,
175 subcommand_list_unused_dblogs,
184 subcommand_setrevprop,
190 enum svnadmin__cmdline_options_t
192 svnadmin__version = SVN_OPT_FIRST_LONGOPT_ID,
193 svnadmin__incremental,
194 svnadmin__keep_going,
196 svnadmin__ignore_uuid,
197 svnadmin__force_uuid,
199 svnadmin__parent_dir,
200 svnadmin__bdb_txn_nosync,
201 svnadmin__bdb_log_keep,
202 svnadmin__config_dir,
203 svnadmin__bypass_hooks,
204 svnadmin__bypass_prop_validation,
205 svnadmin__ignore_dates,
206 svnadmin__use_pre_commit_hook,
207 svnadmin__use_post_commit_hook,
208 svnadmin__use_pre_revprop_change_hook,
209 svnadmin__use_post_revprop_change_hook,
210 svnadmin__clean_logs,
212 svnadmin__pre_1_4_compatible,
213 svnadmin__pre_1_5_compatible,
214 svnadmin__pre_1_6_compatible,
215 svnadmin__compatible_version,
216 svnadmin__check_normalization,
217 svnadmin__metadata_only
220 /* Option codes and descriptions.
222 * The entire list must be terminated with an entry of nulls.
224 static const apr_getopt_option_t options_table[] =
227 N_("show help on a subcommand")},
230 N_("show help on a subcommand")},
232 {"version", svnadmin__version, 0,
233 N_("show program version information")},
236 N_("specify revision number ARG (or X:Y range)")},
238 {"transaction", 't', 1,
239 N_("specify transaction name ARG")},
241 {"incremental", svnadmin__incremental, 0,
242 N_("dump or hotcopy incrementally")},
244 {"deltas", svnadmin__deltas, 0,
245 N_("use deltas in dump output")},
247 {"bypass-hooks", svnadmin__bypass_hooks, 0,
248 N_("bypass the repository hook system")},
250 {"bypass-prop-validation", svnadmin__bypass_prop_validation, 0,
251 N_("bypass property validation logic")},
253 {"ignore-dates", svnadmin__ignore_dates, 0,
254 N_("ignore revision datestamps found in the stream")},
257 N_("no progress (only errors to stderr)")},
259 {"ignore-uuid", svnadmin__ignore_uuid, 0,
260 N_("ignore any repos UUID found in the stream")},
262 {"force-uuid", svnadmin__force_uuid, 0,
263 N_("set repos UUID to that found in stream, if any")},
265 {"fs-type", svnadmin__fs_type, 1,
266 N_("type of repository:\n"
267 " 'fsfs' (default), 'bdb' or 'fsx'\n"
268 " CAUTION: FSX is for EXPERIMENTAL use only!")},
270 {"parent-dir", svnadmin__parent_dir, 1,
271 N_("load at specified directory in repository")},
273 {"bdb-txn-nosync", svnadmin__bdb_txn_nosync, 0,
274 N_("disable fsync at transaction commit [Berkeley DB]")},
276 {"bdb-log-keep", svnadmin__bdb_log_keep, 0,
277 N_("disable automatic log file removal [Berkeley DB]")},
279 {"config-dir", svnadmin__config_dir, 1,
280 N_("read user configuration files from directory ARG")},
282 {"clean-logs", svnadmin__clean_logs, 0,
283 N_("remove redundant Berkeley DB log files\n"
284 " from source repository [Berkeley DB]")},
286 {"use-pre-commit-hook", svnadmin__use_pre_commit_hook, 0,
287 N_("call pre-commit hook before committing revisions")},
289 {"use-post-commit-hook", svnadmin__use_post_commit_hook, 0,
290 N_("call post-commit hook after committing revisions")},
292 {"use-pre-revprop-change-hook", svnadmin__use_pre_revprop_change_hook, 0,
293 N_("call hook before changing revision property")},
295 {"use-post-revprop-change-hook", svnadmin__use_post_revprop_change_hook, 0,
296 N_("call hook after changing revision property")},
298 {"wait", svnadmin__wait, 0,
299 N_("wait instead of exit if the repository is in\n"
300 " use by another process")},
302 {"pre-1.4-compatible", svnadmin__pre_1_4_compatible, 0,
303 N_("deprecated; see --compatible-version")},
305 {"pre-1.5-compatible", svnadmin__pre_1_5_compatible, 0,
306 N_("deprecated; see --compatible-version")},
308 {"pre-1.6-compatible", svnadmin__pre_1_6_compatible, 0,
309 N_("deprecated; see --compatible-version")},
311 {"keep-going", svnadmin__keep_going, 0,
312 N_("continue verification after detecting a corruption")},
314 {"memory-cache-size", 'M', 1,
315 N_("size of the extra in-memory cache in MB used to\n"
316 " minimize redundant operations. Default: 16.\n"
317 " [used for FSFS repositories only]")},
319 {"compatible-version", svnadmin__compatible_version, 1,
320 N_("use repository format compatible with Subversion\n"
321 " version ARG (\"1.5.5\", \"1.7\", etc.)")},
323 {"file", 'F', 1, N_("read repository paths from file ARG")},
325 {"check-normalization", svnadmin__check_normalization, 0,
326 N_("report any names within the same directory or\n"
327 " svn:mergeinfo property value that differ only\n"
328 " in character representation, but are otherwise\n"
331 {"metadata-only", svnadmin__metadata_only, 0,
332 N_("verify metadata only (ignored for BDB),\n"
333 " checking against external corruption in\n"
334 " Subversion 1.9+ format repositories.\n")},
340 /* Array of available subcommands.
341 * The entire list must be terminated with an entry of nulls.
343 static const svn_opt_subcommand_desc2_t cmd_table[] =
345 {"crashtest", subcommand_crashtest, {0}, N_
346 ("usage: svnadmin crashtest REPOS_PATH\n\n"
347 "Open the repository at REPOS_PATH, then abort, thus simulating\n"
348 "a process that crashes while holding an open repository handle.\n"),
351 {"create", subcommand_create, {0}, N_
352 ("usage: svnadmin create REPOS_PATH\n\n"
353 "Create a new, empty repository at REPOS_PATH.\n"),
354 {svnadmin__bdb_txn_nosync, svnadmin__bdb_log_keep,
355 svnadmin__config_dir, svnadmin__fs_type, svnadmin__compatible_version,
356 svnadmin__pre_1_4_compatible, svnadmin__pre_1_5_compatible,
357 svnadmin__pre_1_6_compatible
360 {"delrevprop", subcommand_delrevprop, {0}, N_
361 ("usage: 1. svnadmin delrevprop REPOS_PATH -r REVISION NAME\n"
362 " 2. svnadmin delrevprop REPO_PATH -t TXN NAME\n\n"
363 "1. Delete the property NAME on revision REVISION.\n\n"
364 "Use --use-pre-revprop-change-hook/--use-post-revprop-change-hook to\n"
365 "trigger the revision property-related hooks (for example, if you want\n"
366 "an email notification sent from your post-revprop-change hook).\n\n"
367 "NOTE: Revision properties are not versioned, so this command will\n"
368 "irreversibly destroy the previous value of the property.\n\n"
369 "2. Delete the property NAME on transaction TXN.\n"),
370 {'r', 't', svnadmin__use_pre_revprop_change_hook,
371 svnadmin__use_post_revprop_change_hook} },
373 {"deltify", subcommand_deltify, {0}, N_
374 ("usage: svnadmin deltify [-r LOWER[:UPPER]] REPOS_PATH\n\n"
375 "Run over the requested revision range, performing predecessor delti-\n"
376 "fication on the paths changed in those revisions. Deltification in\n"
377 "essence compresses the repository by only storing the differences or\n"
378 "delta from the preceding revision. If no revisions are specified,\n"
379 "this will simply deltify the HEAD revision.\n"),
382 {"dump", subcommand_dump, {0}, N_
383 ("usage: svnadmin dump REPOS_PATH [-r LOWER[:UPPER] [--incremental]]\n\n"
384 "Dump the contents of filesystem to stdout in a 'dumpfile'\n"
385 "portable format, sending feedback to stderr. Dump revisions\n"
386 "LOWER rev through UPPER rev. If no revisions are given, dump all\n"
387 "revision trees. If only LOWER is given, dump that one revision tree.\n"
388 "If --incremental is passed, the first revision dumped will describe\n"
389 "only the paths changed in that revision; otherwise it will describe\n"
390 "every path present in the repository as of that revision. (In either\n"
391 "case, the second and subsequent revisions, if any, describe only paths\n"
392 "changed in those revisions.)\n"),
393 {'r', svnadmin__incremental, svnadmin__deltas, 'q', 'M'} },
395 {"freeze", subcommand_freeze, {0}, N_
396 ("usage: 1. svnadmin freeze REPOS_PATH PROGRAM [ARG...]\n"
397 " 2. svnadmin freeze -F FILE PROGRAM [ARG...]\n\n"
398 "1. Run PROGRAM passing ARGS while holding a write-lock on REPOS_PATH.\n"
399 " Allows safe use of third-party backup tools on a live repository.\n"
401 "2. Like 1 except all repositories listed in FILE are locked. The file\n"
402 " format is repository paths separated by newlines. Repositories are\n"
403 " locked in the same order as they are listed in the file.\n"),
406 {"help", subcommand_help, {"?", "h"}, N_
407 ("usage: svnadmin help [SUBCOMMAND...]\n\n"
408 "Describe the usage of this program or its subcommands.\n"),
411 {"hotcopy", subcommand_hotcopy, {0}, N_
412 ("usage: svnadmin hotcopy REPOS_PATH NEW_REPOS_PATH\n\n"
413 "Make a hot copy of a repository.\n"
414 "If --incremental is passed, data which already exists at the destination\n"
415 "is not copied again. Incremental mode is implemented for FSFS repositories.\n"),
416 {svnadmin__clean_logs, svnadmin__incremental, 'q'} },
418 {"info", subcommand_info, {0}, N_
419 ("usage: svnadmin info REPOS_PATH\n\n"
420 "Print information about the repository at REPOS_PATH.\n"),
423 {"list-dblogs", subcommand_list_dblogs, {0}, N_
424 ("usage: svnadmin list-dblogs REPOS_PATH\n\n"
425 "List all Berkeley DB log files.\n\n"
426 "WARNING: Modifying or deleting logfiles which are still in use\n"
427 "will cause your repository to be corrupted.\n"),
430 {"list-unused-dblogs", subcommand_list_unused_dblogs, {0}, N_
431 ("usage: svnadmin list-unused-dblogs REPOS_PATH\n\n"
432 "List unused Berkeley DB log files.\n\n"),
435 {"load", subcommand_load, {0}, N_
436 ("usage: svnadmin load REPOS_PATH\n\n"
437 "Read a 'dumpfile'-formatted stream from stdin, committing\n"
438 "new revisions into the repository's filesystem. If the repository\n"
439 "was previously empty, its UUID will, by default, be changed to the\n"
440 "one specified in the stream. Progress feedback is sent to stdout.\n"
441 "If --revision is specified, limit the loaded revisions to only those\n"
442 "in the dump stream whose revision numbers match the specified range.\n"),
443 {'q', 'r', svnadmin__ignore_uuid, svnadmin__force_uuid,
444 svnadmin__ignore_dates,
445 svnadmin__use_pre_commit_hook, svnadmin__use_post_commit_hook,
446 svnadmin__parent_dir, svnadmin__bypass_prop_validation, 'M'} },
448 {"lock", subcommand_lock, {0}, N_
449 ("usage: svnadmin lock REPOS_PATH PATH USERNAME COMMENT-FILE [TOKEN]\n\n"
450 "Lock PATH by USERNAME setting comments from COMMENT-FILE.\n"
451 "If provided, use TOKEN as lock token. Use --bypass-hooks to avoid\n"
452 "triggering the pre-lock and post-lock hook scripts.\n"),
453 {svnadmin__bypass_hooks} },
455 {"lslocks", subcommand_lslocks, {0}, N_
456 ("usage: svnadmin lslocks REPOS_PATH [PATH-IN-REPOS]\n\n"
457 "Print descriptions of all locks on or under PATH-IN-REPOS (which,\n"
458 "if not provided, is the root of the repository).\n"),
461 {"lstxns", subcommand_lstxns, {0}, N_
462 ("usage: svnadmin lstxns REPOS_PATH\n\n"
463 "Print the names of all uncommitted transactions.\n"),
466 {"pack", subcommand_pack, {0}, N_
467 ("usage: svnadmin pack REPOS_PATH\n\n"
468 "Possibly compact the repository into a more efficient storage model.\n"
469 "This may not apply to all repositories, in which case, exit.\n"),
472 {"recover", subcommand_recover, {0}, N_
473 ("usage: svnadmin recover REPOS_PATH\n\n"
474 "Run the recovery procedure on a repository. Do this if you've\n"
475 "been getting errors indicating that recovery ought to be run.\n"
476 "Berkeley DB recovery requires exclusive access and will\n"
477 "exit if the repository is in use by another process.\n"),
480 {"rmlocks", subcommand_rmlocks, {0}, N_
481 ("usage: svnadmin rmlocks REPOS_PATH LOCKED_PATH...\n\n"
482 "Unconditionally remove lock from each LOCKED_PATH.\n"),
485 {"rmtxns", subcommand_rmtxns, {0}, N_
486 ("usage: svnadmin rmtxns REPOS_PATH TXN_NAME...\n\n"
487 "Delete the named transaction(s).\n"),
490 {"setlog", subcommand_setlog, {0}, N_
491 ("usage: svnadmin setlog REPOS_PATH -r REVISION FILE\n\n"
492 "Set the log-message on revision REVISION to the contents of FILE. Use\n"
493 "--bypass-hooks to avoid triggering the revision-property-related hooks\n"
494 "(for example, if you do not want an email notification sent\n"
495 "from your post-revprop-change hook, or because the modification of\n"
496 "revision properties has not been enabled in the pre-revprop-change\n"
498 "NOTE: Revision properties are not versioned, so this command will\n"
499 "overwrite the previous log message.\n"),
500 {'r', svnadmin__bypass_hooks} },
502 {"setrevprop", subcommand_setrevprop, {0}, N_
503 ("usage: 1. svnadmin setrevprop REPOS_PATH -r REVISION NAME FILE\n"
504 " 2. svnadmin setrevprop REPOS_PATH -t TXN NAME FILE\n\n"
505 "1. Set the property NAME on revision REVISION to the contents of FILE.\n\n"
506 "Use --use-pre-revprop-change-hook/--use-post-revprop-change-hook to\n"
507 "trigger the revision property-related hooks (for example, if you want\n"
508 "an email notification sent from your post-revprop-change hook).\n\n"
509 "NOTE: Revision properties are not versioned, so this command will\n"
510 "overwrite the previous value of the property.\n\n"
511 "2. Set the property NAME on transaction TXN to the contents of FILE.\n"),
512 {'r', 't', svnadmin__use_pre_revprop_change_hook,
513 svnadmin__use_post_revprop_change_hook} },
515 {"setuuid", subcommand_setuuid, {0}, N_
516 ("usage: svnadmin setuuid REPOS_PATH [NEW_UUID]\n\n"
517 "Reset the repository UUID for the repository located at REPOS_PATH. If\n"
518 "NEW_UUID is provided, use that as the new repository UUID; otherwise,\n"
519 "generate a brand new UUID for the repository.\n"),
522 {"unlock", subcommand_unlock, {0}, N_
523 ("usage: svnadmin unlock REPOS_PATH LOCKED_PATH USERNAME TOKEN\n\n"
524 "Unlock LOCKED_PATH (as USERNAME) after verifying that the token\n"
525 "associated with the lock matches TOKEN. Use --bypass-hooks to avoid\n"
526 "triggering the pre-unlock and post-unlock hook scripts.\n"),
527 {svnadmin__bypass_hooks} },
529 {"upgrade", subcommand_upgrade, {0}, N_
530 ("usage: svnadmin upgrade REPOS_PATH\n\n"
531 "Upgrade the repository located at REPOS_PATH to the latest supported\n"
532 "schema version.\n\n"
533 "This functionality is provided as a convenience for repository\n"
534 "administrators who wish to make use of new Subversion functionality\n"
535 "without having to undertake a potentially costly full repository dump\n"
536 "and load operation. As such, the upgrade performs only the minimum\n"
537 "amount of work needed to accomplish this while still maintaining the\n"
538 "integrity of the repository. It does not guarantee the most optimized\n"
539 "repository state as a dump and subsequent load would.\n"),
542 {"verify", subcommand_verify, {0}, N_
543 ("usage: svnadmin verify REPOS_PATH\n\n"
544 "Verify the data stored in the repository.\n"),
545 {'t', 'r', 'q', svnadmin__keep_going, 'M',
546 svnadmin__check_normalization, svnadmin__metadata_only} },
548 { NULL, NULL, {0}, NULL, {0} }
552 /* Baton for passing option/argument state to a subcommand function. */
553 struct svnadmin_opt_state
555 const char *repository_path;
556 const char *fs_type; /* --fs-type */
557 svn_version_t *compatible_version; /* --compatible-version */
558 svn_opt_revision_t start_revision, end_revision; /* -r X[:Y] */
559 const char *txn_id; /* -t TXN */
560 svn_boolean_t help; /* --help or -? */
561 svn_boolean_t version; /* --version */
562 svn_boolean_t incremental; /* --incremental */
563 svn_boolean_t use_deltas; /* --deltas */
564 svn_boolean_t use_pre_commit_hook; /* --use-pre-commit-hook */
565 svn_boolean_t use_post_commit_hook; /* --use-post-commit-hook */
566 svn_boolean_t use_pre_revprop_change_hook; /* --use-pre-revprop-change-hook */
567 svn_boolean_t use_post_revprop_change_hook; /* --use-post-revprop-change-hook */
568 svn_boolean_t quiet; /* --quiet */
569 svn_boolean_t bdb_txn_nosync; /* --bdb-txn-nosync */
570 svn_boolean_t bdb_log_keep; /* --bdb-log-keep */
571 svn_boolean_t clean_logs; /* --clean-logs */
572 svn_boolean_t bypass_hooks; /* --bypass-hooks */
573 svn_boolean_t wait; /* --wait */
574 svn_boolean_t keep_going; /* --keep-going */
575 svn_boolean_t check_normalization; /* --check-normalization */
576 svn_boolean_t metadata_only; /* --metadata-only */
577 svn_boolean_t bypass_prop_validation; /* --bypass-prop-validation */
578 svn_boolean_t ignore_dates; /* --ignore-dates */
579 enum svn_repos_load_uuid uuid_action; /* --ignore-uuid,
581 apr_uint64_t memory_cache_size; /* --memory-cache-size M */
582 const char *parent_dir; /* --parent-dir */
583 svn_stringbuf_t *filedata; /* --file */
585 const char *config_dir; /* Overriding Configuration Directory */
589 /* Set *REVNUM to the revision specified by REVISION (or to
590 SVN_INVALID_REVNUM if that has the type 'unspecified'),
591 possibly making use of the YOUNGEST revision number in REPOS. */
593 get_revnum(svn_revnum_t *revnum, const svn_opt_revision_t *revision,
594 svn_revnum_t youngest, svn_repos_t *repos, apr_pool_t *pool)
596 if (revision->kind == svn_opt_revision_number)
597 *revnum = revision->value.number;
598 else if (revision->kind == svn_opt_revision_head)
600 else if (revision->kind == svn_opt_revision_date)
601 SVN_ERR(svn_repos_dated_revision(revnum, repos, revision->value.date,
603 else if (revision->kind == svn_opt_revision_unspecified)
604 *revnum = SVN_INVALID_REVNUM;
606 return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
607 _("Invalid revision specifier"));
609 if (*revnum > youngest)
610 return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
611 _("Revisions must not be greater than the youngest revision (%ld)"),
617 /* Set *PATH to an internal-style, UTF8-encoded, local dirent path
618 allocated from POOL and parsed from raw command-line argument ARG. */
620 target_arg_to_dirent(const char **dirent,
626 SVN_ERR(svn_utf_cstring_to_utf8(&path, arg, pool));
627 if (svn_path_is_url(path))
628 return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
629 _("Path '%s' is not a local path"), path);
630 *dirent = svn_dirent_internal_style(path, pool);
634 /* Parse the remaining command-line arguments from OS, returning them
635 in a new array *ARGS (allocated from POOL) and optionally verifying
636 that we got the expected number thereof. If MIN_EXPECTED is not
637 negative, return an error if the function would return fewer than
638 MIN_EXPECTED arguments. If MAX_EXPECTED is not negative, return an
639 error if the function would return more than MAX_EXPECTED
642 As a special case, when MIN_EXPECTED and MAX_EXPECTED are both 0,
643 allow ARGS to be NULL. */
645 parse_args(apr_array_header_t **args,
651 int num_args = os ? (os->argc - os->ind) : 0;
653 if (min_expected || max_expected)
654 SVN_ERR_ASSERT(args);
656 if ((min_expected >= 0) && (num_args < min_expected))
657 return svn_error_create(SVN_ERR_CL_INSUFFICIENT_ARGS, 0,
658 _("Not enough arguments"));
659 if ((max_expected >= 0) && (num_args > max_expected))
660 return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, 0,
661 _("Too many arguments"));
664 *args = apr_array_make(pool, num_args, sizeof(const char *));
667 while (os->ind < os->argc)
668 APR_ARRAY_PUSH(*args, const char *) =
669 apr_pstrdup(pool, os->argv[os->ind++]);
676 /* This implements 'svn_error_malfunction_handler_t. */
678 crashtest_malfunction_handler(svn_boolean_t can_return,
684 return SVN_NO_ERROR; /* Not reached. */
687 /* This implements `svn_opt_subcommand_t'. */
689 subcommand_crashtest(apr_getopt_t *os, void *baton, apr_pool_t *pool)
691 struct svnadmin_opt_state *opt_state = baton;
694 (void)svn_error_set_malfunction_handler(crashtest_malfunction_handler);
695 SVN_ERR(open_repos(&repos, opt_state->repository_path, pool));
696 SVN_ERR(svn_cmdline_printf(pool,
697 _("Successfully opened repository '%s'.\n"
698 "Will now crash to simulate a crashing "
699 "server process.\n"),
700 svn_dirent_local_style(opt_state->repository_path,
702 SVN_ERR_MALFUNCTION();
704 /* merely silence a compiler warning (this will never be executed) */
708 /* This implements `svn_opt_subcommand_t'. */
710 subcommand_create(apr_getopt_t *os, void *baton, apr_pool_t *pool)
712 struct svnadmin_opt_state *opt_state = baton;
714 apr_hash_t *fs_config = apr_hash_make(pool);
716 /* Expect no more arguments. */
717 SVN_ERR(parse_args(NULL, os, 0, 0, pool));
719 svn_hash_sets(fs_config, SVN_FS_CONFIG_BDB_TXN_NOSYNC,
720 (opt_state->bdb_txn_nosync ? "1" :"0"));
722 svn_hash_sets(fs_config, SVN_FS_CONFIG_BDB_LOG_AUTOREMOVE,
723 (opt_state->bdb_log_keep ? "0" :"1"));
725 if (opt_state->fs_type)
727 /* With 1.8 we are announcing that BDB is deprecated. No support
728 * has been removed and it will continue to work until some future
729 * date. The purpose here is to discourage people from creating
730 * new BDB repositories which they will need to dump/load into
731 * FSFS or some new FS type in the future. */
732 if (0 == strcmp(opt_state->fs_type, SVN_FS_TYPE_BDB))
734 SVN_ERR(svn_cmdline_fprintf(
737 " The \"%s\" repository back-end is deprecated,"
738 " consider using \"%s\" instead.\n"),
739 "svnadmin: ", SVN_FS_TYPE_BDB, SVN_FS_TYPE_FSFS));
742 svn_hash_sets(fs_config, SVN_FS_CONFIG_FS_TYPE, opt_state->fs_type);
745 if (opt_state->compatible_version)
747 if (! svn_version__at_least(opt_state->compatible_version, 1, 4, 0))
748 svn_hash_sets(fs_config, SVN_FS_CONFIG_PRE_1_4_COMPATIBLE, "1");
749 if (! svn_version__at_least(opt_state->compatible_version, 1, 5, 0))
750 svn_hash_sets(fs_config, SVN_FS_CONFIG_PRE_1_5_COMPATIBLE, "1");
751 if (! svn_version__at_least(opt_state->compatible_version, 1, 6, 0))
752 svn_hash_sets(fs_config, SVN_FS_CONFIG_PRE_1_6_COMPATIBLE, "1");
753 if (! svn_version__at_least(opt_state->compatible_version, 1, 8, 0))
754 svn_hash_sets(fs_config, SVN_FS_CONFIG_PRE_1_8_COMPATIBLE, "1");
755 /* In 1.9, we figured out that we didn't have to keep extending this
756 madness indefinitely. */
757 svn_hash_sets(fs_config, SVN_FS_CONFIG_COMPATIBLE_VERSION,
758 apr_psprintf(pool, "%d.%d.%d%s%s",
759 opt_state->compatible_version->major,
760 opt_state->compatible_version->minor,
761 opt_state->compatible_version->patch,
762 opt_state->compatible_version->tag
764 opt_state->compatible_version->tag
765 ? opt_state->compatible_version->tag : ""));
768 if (opt_state->compatible_version)
770 if (! svn_version__at_least(opt_state->compatible_version, 1, 1, 0)
771 /* ### TODO: this NULL check hard-codes knowledge of the library's
772 default fs-type value */
773 && (opt_state->fs_type == NULL
774 || !strcmp(opt_state->fs_type, SVN_FS_TYPE_FSFS)))
776 return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
777 _("Repositories compatible with 1.0.x must "
778 "use --fs-type=bdb"));
781 if (! svn_version__at_least(opt_state->compatible_version, 1, 9, 0)
782 && opt_state->fs_type && !strcmp(opt_state->fs_type, SVN_FS_TYPE_FSX))
784 return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
785 _("Repositories compatible with 1.8.x or "
786 "earlier cannot use --fs-type=%s"),
791 SVN_ERR(svn_repos_create(&repos, opt_state->repository_path,
792 NULL, NULL, NULL, fs_config, pool));
793 svn_fs_set_warning_func(svn_repos_fs(repos), warning_func, NULL);
798 /* This implements `svn_opt_subcommand_t'. */
800 subcommand_deltify(apr_getopt_t *os, void *baton, apr_pool_t *pool)
802 struct svnadmin_opt_state *opt_state = baton;
805 svn_revnum_t start = SVN_INVALID_REVNUM, end = SVN_INVALID_REVNUM;
806 svn_revnum_t youngest, revision;
807 apr_pool_t *subpool = svn_pool_create(pool);
809 /* Expect no more arguments. */
810 SVN_ERR(parse_args(NULL, os, 0, 0, pool));
812 SVN_ERR(open_repos(&repos, opt_state->repository_path, pool));
813 fs = svn_repos_fs(repos);
814 SVN_ERR(svn_fs_youngest_rev(&youngest, fs, pool));
816 /* Find the revision numbers at which to start and end. */
817 SVN_ERR(get_revnum(&start, &opt_state->start_revision,
818 youngest, repos, pool));
819 SVN_ERR(get_revnum(&end, &opt_state->end_revision,
820 youngest, repos, pool));
822 /* Fill in implied revisions if necessary. */
823 if (start == SVN_INVALID_REVNUM)
825 if (end == SVN_INVALID_REVNUM)
829 return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
830 _("First revision cannot be higher than second"));
832 /* Loop over the requested revision range, performing the
833 predecessor deltification on paths changed in each. */
834 for (revision = start; revision <= end; revision++)
836 svn_pool_clear(subpool);
837 SVN_ERR(check_cancel(NULL));
838 if (! opt_state->quiet)
839 SVN_ERR(svn_cmdline_printf(subpool, _("Deltifying revision %ld..."),
841 SVN_ERR(svn_fs_deltify_revision(fs, revision, subpool));
842 if (! opt_state->quiet)
843 SVN_ERR(svn_cmdline_printf(subpool, _("done.\n")));
845 svn_pool_destroy(subpool);
850 /* Structure for errors encountered during 'svnadmin verify --keep-going'. */
851 struct verification_error
857 /* Pool cleanup function to clear an svn_error_t *. */
859 err_cleanup(void *data)
861 svn_error_t *err = data;
863 svn_error_clear(err);
868 struct repos_verify_callback_baton
870 /* Should we continue after receiving a first verification error? */
871 svn_boolean_t keep_going;
873 /* List of errors encountered during 'svnadmin verify --keep-going'. */
874 apr_array_header_t *error_summary;
876 /* Pool for data collected during callback invocations. */
877 apr_pool_t *result_pool;
880 /* Implementation of svn_repos_verify_callback_t to handle errors coming
881 from svn_repos_verify_fs3(). */
883 repos_verify_callback(void *baton,
884 svn_revnum_t revision,
885 svn_error_t *verify_err,
886 apr_pool_t *scratch_pool)
888 struct repos_verify_callback_baton *b = baton;
890 if (revision == SVN_INVALID_REVNUM)
892 SVN_ERR(svn_cmdline_fputs(_("* Error verifying repository metadata.\n"),
893 stderr, scratch_pool));
897 SVN_ERR(svn_cmdline_fprintf(stderr, scratch_pool,
898 _("* Error verifying revision %ld.\n"),
904 struct verification_error *verr;
906 svn_handle_error2(verify_err, stderr, FALSE, "svnadmin: ");
908 /* Remember the error in B->ERROR_SUMMARY. */
909 verr = apr_palloc(b->result_pool, sizeof(*verr));
910 verr->rev = revision;
911 verr->err = svn_error_dup(verify_err);
912 apr_pool_cleanup_register(b->result_pool, verr->err, err_cleanup,
913 apr_pool_cleanup_null);
914 APR_ARRAY_PUSH(b->error_summary, struct verification_error *) = verr;
919 return svn_error_trace(svn_error_dup(verify_err));
922 /* Implementation of svn_repos_notify_func_t to wrap the output to a
923 response stream for svn_repos_dump_fs2(), svn_repos_verify_fs(),
924 svn_repos_hotcopy3() and others. */
926 repos_notify_handler(void *baton,
927 const svn_repos_notify_t *notify,
928 apr_pool_t *scratch_pool)
930 svn_stream_t *feedback_stream = baton;
932 switch (notify->action)
934 case svn_repos_notify_warning:
935 svn_error_clear(svn_stream_printf(feedback_stream, scratch_pool,
936 "WARNING 0x%04x: %s\n", notify->warning,
937 notify->warning_str));
940 case svn_repos_notify_dump_rev_end:
941 svn_error_clear(svn_stream_printf(feedback_stream, scratch_pool,
942 _("* Dumped revision %ld.\n"),
946 case svn_repos_notify_verify_rev_end:
947 svn_error_clear(svn_stream_printf(feedback_stream, scratch_pool,
948 _("* Verified revision %ld.\n"),
952 case svn_repos_notify_verify_rev_structure:
953 if (notify->revision == SVN_INVALID_REVNUM)
954 svn_error_clear(svn_stream_puts(feedback_stream,
955 _("* Verifying repository metadata ...\n")));
957 svn_error_clear(svn_stream_printf(feedback_stream, scratch_pool,
958 _("* Verifying metadata at revision %ld ...\n"),
962 case svn_repos_notify_pack_shard_start:
964 const char *shardstr = apr_psprintf(scratch_pool,
967 svn_error_clear(svn_stream_printf(feedback_stream, scratch_pool,
968 _("Packing revisions in shard %s..."),
973 case svn_repos_notify_pack_shard_end:
974 svn_error_clear(svn_stream_puts(feedback_stream, _("done.\n")));
977 case svn_repos_notify_pack_shard_start_revprop:
979 const char *shardstr = apr_psprintf(scratch_pool,
982 svn_error_clear(svn_stream_printf(feedback_stream, scratch_pool,
983 _("Packing revprops in shard %s..."),
988 case svn_repos_notify_pack_shard_end_revprop:
989 svn_error_clear(svn_stream_puts(feedback_stream, _("done.\n")));
992 case svn_repos_notify_load_txn_committed:
993 if (notify->old_revision == SVN_INVALID_REVNUM)
995 svn_error_clear(svn_stream_printf(feedback_stream, scratch_pool,
996 _("\n------- Committed revision %ld >>>\n\n"),
997 notify->new_revision));
1001 svn_error_clear(svn_stream_printf(feedback_stream, scratch_pool,
1002 _("\n------- Committed new rev %ld"
1003 " (loaded from original rev %ld"
1004 ") >>>\n\n"), notify->new_revision,
1005 notify->old_revision));
1009 case svn_repos_notify_load_node_start:
1011 switch (notify->node_action)
1013 case svn_node_action_change:
1014 svn_error_clear(svn_stream_printf(feedback_stream, scratch_pool,
1015 _(" * editing path : %s ..."),
1019 case svn_node_action_delete:
1020 svn_error_clear(svn_stream_printf(feedback_stream, scratch_pool,
1021 _(" * deleting path : %s ..."),
1025 case svn_node_action_add:
1026 svn_error_clear(svn_stream_printf(feedback_stream, scratch_pool,
1027 _(" * adding path : %s ..."),
1031 case svn_node_action_replace:
1032 svn_error_clear(svn_stream_printf(feedback_stream, scratch_pool,
1033 _(" * replacing path : %s ..."),
1041 case svn_repos_notify_load_node_done:
1042 svn_error_clear(svn_stream_puts(feedback_stream, _(" done.\n")));
1045 case svn_repos_notify_load_copied_node:
1046 svn_error_clear(svn_stream_puts(feedback_stream, "COPIED..."));
1049 case svn_repos_notify_load_txn_start:
1050 svn_error_clear(svn_stream_printf(feedback_stream, scratch_pool,
1051 _("<<< Started new transaction, based on "
1052 "original revision %ld\n"),
1053 notify->old_revision));
1056 case svn_repos_notify_load_skipped_rev:
1057 svn_error_clear(svn_stream_printf(feedback_stream, scratch_pool,
1058 _("<<< Skipped original revision %ld\n"),
1059 notify->old_revision));
1062 case svn_repos_notify_load_normalized_mergeinfo:
1063 svn_error_clear(svn_stream_printf(feedback_stream, scratch_pool,
1064 _(" removing '\\r' from %s ..."),
1065 SVN_PROP_MERGEINFO));
1068 case svn_repos_notify_mutex_acquired:
1069 /* Enable cancellation signal handlers. */
1070 setup_cancellation_signals(signal_handler);
1073 case svn_repos_notify_recover_start:
1074 svn_error_clear(svn_stream_puts(feedback_stream,
1075 _("Repository lock acquired.\n"
1076 "Please wait; recovering the"
1077 " repository may take some time...\n")));
1080 case svn_repos_notify_upgrade_start:
1081 svn_error_clear(svn_stream_puts(feedback_stream,
1082 _("Repository lock acquired.\n"
1083 "Please wait; upgrading the"
1084 " repository may take some time...\n")));
1087 case svn_repos_notify_pack_revprops:
1089 const char *shardstr = apr_psprintf(scratch_pool,
1090 "%" APR_INT64_T_FMT,
1092 svn_error_clear(svn_stream_printf(feedback_stream, scratch_pool,
1093 _("Packed revision properties in shard %s\n"),
1098 case svn_repos_notify_cleanup_revprops:
1100 const char *shardstr = apr_psprintf(scratch_pool,
1101 "%" APR_INT64_T_FMT,
1103 svn_error_clear(svn_stream_printf(feedback_stream, scratch_pool,
1104 _("Removed non-packed revision properties"
1110 case svn_repos_notify_format_bumped:
1111 svn_error_clear(svn_stream_printf(feedback_stream, scratch_pool,
1112 _("Bumped repository format to %ld\n"),
1116 case svn_repos_notify_hotcopy_rev_range:
1117 if (notify->start_revision == notify->end_revision)
1119 svn_error_clear(svn_stream_printf(feedback_stream, scratch_pool,
1120 _("* Copied revision %ld.\n"),
1121 notify->start_revision));
1125 svn_error_clear(svn_stream_printf(feedback_stream, scratch_pool,
1126 _("* Copied revisions from %ld to %ld.\n"),
1127 notify->start_revision, notify->end_revision));
1136 /* Baton for recode_write(). */
1137 struct recode_write_baton
1143 /* This implements the 'svn_write_fn_t' interface.
1145 Write DATA to ((struct recode_write_baton *) BATON)->out, in the
1146 console encoding, using svn_cmdline_fprintf(). DATA is a
1147 UTF8-encoded C string, therefore ignore LEN.
1149 ### This recoding mechanism might want to be abstracted into
1150 ### svn_io.h or svn_cmdline.h, if it proves useful elsewhere. */
1151 static svn_error_t *recode_write(void *baton,
1155 struct recode_write_baton *rwb = baton;
1156 svn_pool_clear(rwb->pool);
1157 return svn_cmdline_fputs(data, rwb->out, rwb->pool);
1160 /* Create a stream, to write to STD_STREAM, that uses recode_write()
1161 to perform UTF-8 to console encoding translation. */
1162 static svn_stream_t *
1163 recode_stream_create(FILE *std_stream, apr_pool_t *pool)
1165 struct recode_write_baton *std_stream_rwb =
1166 apr_palloc(pool, sizeof(struct recode_write_baton));
1168 svn_stream_t *rw_stream = svn_stream_create(std_stream_rwb, pool);
1169 std_stream_rwb->pool = svn_pool_create(pool);
1170 std_stream_rwb->out = std_stream;
1171 svn_stream_set_write(rw_stream, recode_write);
1176 /* This implements `svn_opt_subcommand_t'. */
1177 static svn_error_t *
1178 subcommand_dump(apr_getopt_t *os, void *baton, apr_pool_t *pool)
1180 struct svnadmin_opt_state *opt_state = baton;
1183 svn_stream_t *stdout_stream;
1184 svn_revnum_t lower = SVN_INVALID_REVNUM, upper = SVN_INVALID_REVNUM;
1185 svn_revnum_t youngest;
1186 svn_stream_t *feedback_stream = NULL;
1188 /* Expect no more arguments. */
1189 SVN_ERR(parse_args(NULL, os, 0, 0, pool));
1191 SVN_ERR(open_repos(&repos, opt_state->repository_path, pool));
1192 fs = svn_repos_fs(repos);
1193 SVN_ERR(svn_fs_youngest_rev(&youngest, fs, pool));
1195 /* Find the revision numbers at which to start and end. */
1196 SVN_ERR(get_revnum(&lower, &opt_state->start_revision,
1197 youngest, repos, pool));
1198 SVN_ERR(get_revnum(&upper, &opt_state->end_revision,
1199 youngest, repos, pool));
1201 /* Fill in implied revisions if necessary. */
1202 if (lower == SVN_INVALID_REVNUM)
1207 else if (upper == SVN_INVALID_REVNUM)
1213 return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
1214 _("First revision cannot be higher than second"));
1216 SVN_ERR(svn_stream_for_stdout(&stdout_stream, pool));
1218 /* Progress feedback goes to STDERR, unless they asked to suppress it. */
1219 if (! opt_state->quiet)
1220 feedback_stream = recode_stream_create(stderr, pool);
1222 SVN_ERR(svn_repos_dump_fs3(repos, stdout_stream, lower, upper,
1223 opt_state->incremental, opt_state->use_deltas,
1224 !opt_state->quiet ? repos_notify_handler : NULL,
1225 feedback_stream, check_cancel, NULL, pool));
1227 return SVN_NO_ERROR;
1230 struct freeze_baton_t {
1231 const char *command;
1236 /* Implements svn_repos_freeze_func_t */
1237 static svn_error_t *
1238 freeze_body(void *baton,
1241 struct freeze_baton_t *b = baton;
1242 apr_status_t apr_err;
1243 apr_file_t *infile, *outfile, *errfile;
1245 apr_err = apr_file_open_stdin(&infile, pool);
1247 return svn_error_wrap_apr(apr_err, "Can't open stdin");
1248 apr_err = apr_file_open_stdout(&outfile, pool);
1250 return svn_error_wrap_apr(apr_err, "Can't open stdout");
1251 apr_err = apr_file_open_stderr(&errfile, pool);
1253 return svn_error_wrap_apr(apr_err, "Can't open stderr");
1255 SVN_ERR(svn_io_run_cmd(NULL, b->command, b->args, &b->status,
1257 infile, outfile, errfile, pool));
1259 return SVN_NO_ERROR;
1262 static svn_error_t *
1263 subcommand_freeze(apr_getopt_t *os, void *baton, apr_pool_t *pool)
1265 struct svnadmin_opt_state *opt_state = baton;
1266 apr_array_header_t *paths;
1267 apr_array_header_t *args;
1269 struct freeze_baton_t b;
1271 SVN_ERR(svn_opt_parse_all_args(&args, os, pool));
1274 return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, 0,
1275 _("No program provided"));
1277 if (!opt_state->filedata)
1279 /* One repository on the command line. */
1280 paths = apr_array_make(pool, 1, sizeof(const char *));
1281 APR_ARRAY_PUSH(paths, const char *) = opt_state->repository_path;
1286 /* All repositories in filedata. */
1287 SVN_ERR(svn_utf_cstring_to_utf8(&utf8, opt_state->filedata->data, pool));
1288 paths = svn_cstring_split(utf8, "\r\n", FALSE, pool);
1291 b.command = APR_ARRAY_IDX(args, 0, const char *);
1292 b.args = apr_palloc(pool, sizeof(char *) * (args->nelts + 1));
1293 for (i = 0; i < args->nelts; ++i)
1294 b.args[i] = APR_ARRAY_IDX(args, i, const char *);
1295 b.args[args->nelts] = NULL;
1297 SVN_ERR(svn_repos_freeze(paths, freeze_body, &b, pool));
1299 /* Make any non-zero status visible to the user. */
1303 return SVN_NO_ERROR;
1307 /* This implements `svn_opt_subcommand_t'. */
1308 static svn_error_t *
1309 subcommand_help(apr_getopt_t *os, void *baton, apr_pool_t *pool)
1311 struct svnadmin_opt_state *opt_state = baton;
1312 const char *header =
1313 _("general usage: svnadmin SUBCOMMAND REPOS_PATH [ARGS & OPTIONS ...]\n"
1314 "Subversion repository administration tool.\n"
1315 "Type 'svnadmin help <subcommand>' for help on a specific subcommand.\n"
1316 "Type 'svnadmin --version' to see the program version and FS modules.\n"
1318 "Available subcommands:\n");
1320 const char *fs_desc_start
1321 = _("The following repository back-end (FS) modules are available:\n\n");
1323 svn_stringbuf_t *version_footer;
1325 version_footer = svn_stringbuf_create(fs_desc_start, pool);
1326 SVN_ERR(svn_fs_print_modules(version_footer, pool));
1328 SVN_ERR(svn_opt_print_help4(os, "svnadmin",
1329 opt_state ? opt_state->version : FALSE,
1330 opt_state ? opt_state->quiet : FALSE,
1331 /*###opt_state ? opt_state->verbose :*/ FALSE,
1332 version_footer->data,
1333 header, cmd_table, options_table, NULL, NULL,
1336 return SVN_NO_ERROR;
1340 /* Set *REVNUM to the revision number of a numeric REV, or to
1341 SVN_INVALID_REVNUM if REV is unspecified. */
1342 static svn_error_t *
1343 optrev_to_revnum(svn_revnum_t *revnum, const svn_opt_revision_t *opt_rev)
1345 if (opt_rev->kind == svn_opt_revision_number)
1347 *revnum = opt_rev->value.number;
1348 if (! SVN_IS_VALID_REVNUM(*revnum))
1349 return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
1350 _("Invalid revision number (%ld) specified"),
1353 else if (opt_rev->kind == svn_opt_revision_unspecified)
1355 *revnum = SVN_INVALID_REVNUM;
1359 return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
1360 _("Non-numeric revision specified"));
1362 return SVN_NO_ERROR;
1366 /* This implements `svn_opt_subcommand_t'. */
1367 static svn_error_t *
1368 subcommand_load(apr_getopt_t *os, void *baton, apr_pool_t *pool)
1371 struct svnadmin_opt_state *opt_state = baton;
1373 svn_revnum_t lower = SVN_INVALID_REVNUM, upper = SVN_INVALID_REVNUM;
1374 svn_stream_t *stdin_stream;
1375 svn_stream_t *feedback_stream = NULL;
1377 /* Expect no more arguments. */
1378 SVN_ERR(parse_args(NULL, os, 0, 0, pool));
1380 /* Find the revision numbers at which to start and end. We only
1381 support a limited set of revision kinds: number and unspecified. */
1382 SVN_ERR(optrev_to_revnum(&lower, &opt_state->start_revision));
1383 SVN_ERR(optrev_to_revnum(&upper, &opt_state->end_revision));
1385 /* Fill in implied revisions if necessary. */
1386 if ((upper == SVN_INVALID_REVNUM) && (lower != SVN_INVALID_REVNUM))
1390 else if ((upper != SVN_INVALID_REVNUM) && (lower == SVN_INVALID_REVNUM))
1395 /* Ensure correct range ordering. */
1398 return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
1399 _("First revision cannot be higher than second"));
1402 SVN_ERR(open_repos(&repos, opt_state->repository_path, pool));
1404 /* Read the stream from STDIN. Users can redirect a file. */
1405 SVN_ERR(svn_stream_for_stdin(&stdin_stream, pool));
1407 /* Progress feedback goes to STDOUT, unless they asked to suppress it. */
1408 if (! opt_state->quiet)
1409 feedback_stream = recode_stream_create(stdout, pool);
1411 err = svn_repos_load_fs5(repos, stdin_stream, lower, upper,
1412 opt_state->uuid_action, opt_state->parent_dir,
1413 opt_state->use_pre_commit_hook,
1414 opt_state->use_post_commit_hook,
1415 !opt_state->bypass_prop_validation,
1416 opt_state->ignore_dates,
1417 opt_state->quiet ? NULL : repos_notify_handler,
1418 feedback_stream, check_cancel, NULL, pool);
1419 if (err && err->apr_err == SVN_ERR_BAD_PROPERTY_VALUE)
1420 return svn_error_quick_wrap(err,
1421 _("Invalid property value found in "
1422 "dumpstream; consider repairing the source "
1423 "or using --bypass-prop-validation while "
1429 /* This implements `svn_opt_subcommand_t'. */
1430 static svn_error_t *
1431 subcommand_lstxns(apr_getopt_t *os, void *baton, apr_pool_t *pool)
1433 struct svnadmin_opt_state *opt_state = baton;
1436 apr_array_header_t *txns;
1439 /* Expect no more arguments. */
1440 SVN_ERR(parse_args(NULL, os, 0, 0, pool));
1442 SVN_ERR(open_repos(&repos, opt_state->repository_path, pool));
1443 fs = svn_repos_fs(repos);
1444 SVN_ERR(svn_fs_list_transactions(&txns, fs, pool));
1446 /* Loop, printing revisions. */
1447 for (i = 0; i < txns->nelts; i++)
1449 SVN_ERR(svn_cmdline_printf(pool, "%s\n",
1450 APR_ARRAY_IDX(txns, i, const char *)));
1453 return SVN_NO_ERROR;
1457 /* This implements `svn_opt_subcommand_t'. */
1458 static svn_error_t *
1459 subcommand_recover(apr_getopt_t *os, void *baton, apr_pool_t *pool)
1461 svn_revnum_t youngest_rev;
1464 struct svnadmin_opt_state *opt_state = baton;
1465 svn_stream_t *feedback_stream = NULL;
1467 /* Expect no more arguments. */
1468 SVN_ERR(parse_args(NULL, os, 0, 0, pool));
1470 SVN_ERR(svn_stream_for_stdout(&feedback_stream, pool));
1472 /* Restore default signal handlers until after we have acquired the
1473 * exclusive lock so that the user interrupt before we actually
1474 * touch the repository. */
1475 setup_cancellation_signals(SIG_DFL);
1477 err = svn_repos_recover4(opt_state->repository_path, TRUE,
1478 repos_notify_handler, feedback_stream,
1479 check_cancel, NULL, pool);
1482 if (! APR_STATUS_IS_EAGAIN(err->apr_err))
1484 svn_error_clear(err);
1485 if (! opt_state->wait)
1486 return svn_error_create(SVN_ERR_REPOS_LOCKED, NULL,
1487 _("Failed to get exclusive repository "
1488 "access; perhaps another process\n"
1489 "such as httpd, svnserve or svn "
1491 SVN_ERR(svn_cmdline_printf(pool,
1492 _("Waiting on repository lock; perhaps"
1493 " another process has it open?\n")));
1494 SVN_ERR(svn_cmdline_fflush(stdout));
1495 SVN_ERR(svn_repos_recover4(opt_state->repository_path, FALSE,
1496 repos_notify_handler, feedback_stream,
1497 check_cancel, NULL, pool));
1500 SVN_ERR(svn_cmdline_printf(pool, _("\nRecovery completed.\n")));
1502 /* Since db transactions may have been replayed, it's nice to tell
1503 people what the latest revision is. It also proves that the
1504 recovery actually worked. */
1505 SVN_ERR(open_repos(&repos, opt_state->repository_path, pool));
1506 SVN_ERR(svn_fs_youngest_rev(&youngest_rev, svn_repos_fs(repos), pool));
1507 SVN_ERR(svn_cmdline_printf(pool, _("The latest repos revision is %ld.\n"),
1510 return SVN_NO_ERROR;
1514 /* This implements `svn_opt_subcommand_t'. */
1515 static svn_error_t *
1516 list_dblogs(apr_getopt_t *os, void *baton, svn_boolean_t only_unused,
1519 struct svnadmin_opt_state *opt_state = baton;
1520 apr_array_header_t *logfiles;
1523 /* Expect no more arguments. */
1524 SVN_ERR(parse_args(NULL, os, 0, 0, pool));
1526 SVN_ERR(svn_repos_db_logfiles(&logfiles,
1527 opt_state->repository_path,
1531 /* Loop, printing log files. We append the log paths to the
1532 repository path, making sure to return everything to the native
1533 style before printing. */
1534 for (i = 0; i < logfiles->nelts; i++)
1536 const char *log_utf8;
1537 log_utf8 = svn_dirent_join(opt_state->repository_path,
1538 APR_ARRAY_IDX(logfiles, i, const char *),
1540 log_utf8 = svn_dirent_local_style(log_utf8, pool);
1541 SVN_ERR(svn_cmdline_printf(pool, "%s\n", log_utf8));
1544 return SVN_NO_ERROR;
1548 /* This implements `svn_opt_subcommand_t'. */
1549 static svn_error_t *
1550 subcommand_list_dblogs(apr_getopt_t *os, void *baton, apr_pool_t *pool)
1552 SVN_ERR(list_dblogs(os, baton, FALSE, pool));
1553 return SVN_NO_ERROR;
1557 /* This implements `svn_opt_subcommand_t'. */
1558 static svn_error_t *
1559 subcommand_list_unused_dblogs(apr_getopt_t *os, void *baton, apr_pool_t *pool)
1561 /* Expect no more arguments. */
1562 SVN_ERR(parse_args(NULL, os, 0, 0, pool));
1564 SVN_ERR(list_dblogs(os, baton, TRUE, pool));
1565 return SVN_NO_ERROR;
1569 /* This implements `svn_opt_subcommand_t'. */
1570 static svn_error_t *
1571 subcommand_rmtxns(apr_getopt_t *os, void *baton, apr_pool_t *pool)
1573 struct svnadmin_opt_state *opt_state = baton;
1577 apr_array_header_t *args;
1579 apr_pool_t *subpool = svn_pool_create(pool);
1581 SVN_ERR(svn_opt_parse_all_args(&args, os, pool));
1583 SVN_ERR(open_repos(&repos, opt_state->repository_path, pool));
1584 fs = svn_repos_fs(repos);
1586 /* All the rest of the arguments are transaction names. */
1587 for (i = 0; i < args->nelts; i++)
1589 const char *txn_name = APR_ARRAY_IDX(args, i, const char *);
1590 const char *txn_name_utf8;
1593 svn_pool_clear(subpool);
1595 SVN_ERR(svn_utf_cstring_to_utf8(&txn_name_utf8, txn_name, subpool));
1597 /* Try to open the txn. If that succeeds, try to abort it. */
1598 err = svn_fs_open_txn(&txn, fs, txn_name_utf8, subpool);
1600 err = svn_fs_abort_txn(txn, subpool);
1602 /* If either the open or the abort of the txn fails because that
1603 transaction is dead, just try to purge the thing. Else,
1604 there was either an error worth reporting, or not error at
1606 if (err && (err->apr_err == SVN_ERR_FS_TRANSACTION_DEAD))
1608 svn_error_clear(err);
1609 err = svn_fs_purge_txn(fs, txn_name_utf8, subpool);
1612 /* If we had a real from the txn open, abort, or purge, we clear
1613 that error and just report to the user that we had an issue
1614 with this particular txn. */
1617 svn_handle_error2(err, stderr, FALSE /* non-fatal */, "svnadmin: ");
1618 svn_error_clear(err);
1620 else if (! opt_state->quiet)
1622 SVN_ERR(svn_cmdline_printf(subpool, _("Transaction '%s' removed.\n"),
1627 svn_pool_destroy(subpool);
1629 return SVN_NO_ERROR;
1633 /* A helper for the 'setrevprop' and 'setlog' commands. Expects
1634 OPT_STATE->txn_id, OPT_STATE->use_pre_revprop_change_hook and
1635 OPT_STATE->use_post_revprop_change_hook to be set appropriately.
1636 If FILENAME is NULL, delete property PROP_NAME. */
1637 static svn_error_t *
1638 set_revprop(const char *prop_name, const char *filename,
1639 struct svnadmin_opt_state *opt_state, apr_pool_t *pool)
1642 svn_string_t *prop_value;
1646 svn_stringbuf_t *file_contents;
1648 SVN_ERR(svn_stringbuf_from_file2(&file_contents, filename, pool));
1650 prop_value = svn_string_create_empty(pool);
1651 prop_value->data = file_contents->data;
1652 prop_value->len = file_contents->len;
1654 SVN_ERR(svn_subst_translate_string2(&prop_value, NULL, NULL, prop_value,
1655 NULL, FALSE, pool, pool));
1662 /* Open the filesystem */
1663 SVN_ERR(open_repos(&repos, opt_state->repository_path, pool));
1665 if (opt_state->txn_id)
1667 svn_fs_t *fs = svn_repos_fs(repos);
1670 SVN_ERR(svn_fs_open_txn(&txn, fs, opt_state->txn_id, pool));
1671 SVN_ERR(svn_fs_change_txn_prop(txn, prop_name, prop_value, pool));
1674 SVN_ERR(svn_repos_fs_change_rev_prop4(
1675 repos, opt_state->start_revision.value.number,
1676 NULL, prop_name, NULL, prop_value,
1677 opt_state->use_pre_revprop_change_hook,
1678 opt_state->use_post_revprop_change_hook,
1681 return SVN_NO_ERROR;
1685 /* This implements `svn_opt_subcommand_t'. */
1686 static svn_error_t *
1687 subcommand_setrevprop(apr_getopt_t *os, void *baton, apr_pool_t *pool)
1689 struct svnadmin_opt_state *opt_state = baton;
1690 apr_array_header_t *args;
1691 const char *prop_name, *filename;
1693 /* Expect two more arguments: NAME FILE */
1694 SVN_ERR(parse_args(&args, os, 2, 2, pool));
1695 prop_name = APR_ARRAY_IDX(args, 0, const char *);
1696 filename = APR_ARRAY_IDX(args, 1, const char *);
1697 SVN_ERR(target_arg_to_dirent(&filename, filename, pool));
1699 if (opt_state->txn_id)
1701 if (opt_state->start_revision.kind != svn_opt_revision_unspecified
1702 || opt_state->end_revision.kind != svn_opt_revision_unspecified)
1703 return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
1704 _("--revision (-r) and --transaction (-t) "
1705 "are mutually exclusive"));
1707 if (opt_state->use_pre_revprop_change_hook
1708 || opt_state->use_post_revprop_change_hook)
1709 return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
1710 _("Calling hooks is incompatible with "
1711 "--transaction (-t)"));
1713 else if (opt_state->start_revision.kind != svn_opt_revision_number)
1714 return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
1715 _("Missing revision"));
1716 else if (opt_state->end_revision.kind != svn_opt_revision_unspecified)
1717 return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
1718 _("Only one revision allowed"));
1720 return set_revprop(prop_name, filename, opt_state, pool);
1724 /* This implements `svn_opt_subcommand_t'. */
1725 static svn_error_t *
1726 subcommand_setuuid(apr_getopt_t *os, void *baton, apr_pool_t *pool)
1728 struct svnadmin_opt_state *opt_state = baton;
1729 apr_array_header_t *args;
1732 const char *uuid = NULL;
1734 /* Expect zero or one more arguments: [UUID] */
1735 SVN_ERR(parse_args(&args, os, 0, 1, pool));
1736 if (args->nelts == 1)
1737 uuid = APR_ARRAY_IDX(args, 0, const char *);
1739 SVN_ERR(open_repos(&repos, opt_state->repository_path, pool));
1740 fs = svn_repos_fs(repos);
1741 return svn_fs_set_uuid(fs, uuid, pool);
1745 /* This implements `svn_opt_subcommand_t'. */
1746 static svn_error_t *
1747 subcommand_setlog(apr_getopt_t *os, void *baton, apr_pool_t *pool)
1749 struct svnadmin_opt_state *opt_state = baton;
1750 apr_array_header_t *args;
1751 const char *filename;
1753 /* Expect one more argument: FILE */
1754 SVN_ERR(parse_args(&args, os, 1, 1, pool));
1755 filename = APR_ARRAY_IDX(args, 0, const char *);
1756 SVN_ERR(target_arg_to_dirent(&filename, filename, pool));
1758 if (opt_state->start_revision.kind != svn_opt_revision_number)
1759 return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
1760 _("Missing revision"));
1761 else if (opt_state->end_revision.kind != svn_opt_revision_unspecified)
1762 return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
1763 _("Only one revision allowed"));
1765 /* set_revprop() responds only to pre-/post-revprop-change opts. */
1766 if (!opt_state->bypass_hooks)
1768 opt_state->use_pre_revprop_change_hook = TRUE;
1769 opt_state->use_post_revprop_change_hook = TRUE;
1772 return set_revprop(SVN_PROP_REVISION_LOG, filename, opt_state, pool);
1776 /* This implements 'svn_opt_subcommand_t'. */
1777 static svn_error_t *
1778 subcommand_pack(apr_getopt_t *os, void *baton, apr_pool_t *pool)
1780 struct svnadmin_opt_state *opt_state = baton;
1782 svn_stream_t *feedback_stream = NULL;
1784 /* Expect no more arguments. */
1785 SVN_ERR(parse_args(NULL, os, 0, 0, pool));
1787 SVN_ERR(open_repos(&repos, opt_state->repository_path, pool));
1789 /* Progress feedback goes to STDOUT, unless they asked to suppress it. */
1790 if (! opt_state->quiet)
1791 feedback_stream = recode_stream_create(stdout, pool);
1793 return svn_error_trace(
1794 svn_repos_fs_pack2(repos, !opt_state->quiet ? repos_notify_handler : NULL,
1795 feedback_stream, check_cancel, NULL, pool));
1799 /* This implements `svn_opt_subcommand_t'. */
1800 static svn_error_t *
1801 subcommand_verify(apr_getopt_t *os, void *baton, apr_pool_t *pool)
1803 struct svnadmin_opt_state *opt_state = baton;
1806 svn_revnum_t youngest, lower, upper;
1807 svn_stream_t *feedback_stream = NULL;
1808 struct repos_verify_callback_baton verify_baton = { 0 };
1810 /* Expect no more arguments. */
1811 SVN_ERR(parse_args(NULL, os, 0, 0, pool));
1813 if (opt_state->txn_id
1814 && (opt_state->start_revision.kind != svn_opt_revision_unspecified
1815 || opt_state->end_revision.kind != svn_opt_revision_unspecified))
1817 return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
1818 _("--revision (-r) and --transaction (-t) "
1819 "are mutually exclusive"));
1822 SVN_ERR(open_repos(&repos, opt_state->repository_path, pool));
1823 fs = svn_repos_fs(repos);
1824 SVN_ERR(svn_fs_youngest_rev(&youngest, fs, pool));
1827 if (opt_state->txn_id)
1830 svn_fs_root_t *root;
1832 SVN_ERR(svn_fs_open_txn(&txn, fs, opt_state->txn_id, pool));
1833 SVN_ERR(svn_fs_txn_root(&root, txn, pool));
1834 SVN_ERR(svn_fs_verify_root(root, pool));
1835 return SVN_NO_ERROR;
1841 /* Find the revision numbers at which to start and end. */
1842 SVN_ERR(get_revnum(&lower, &opt_state->start_revision,
1843 youngest, repos, pool));
1844 SVN_ERR(get_revnum(&upper, &opt_state->end_revision,
1845 youngest, repos, pool));
1847 if (upper == SVN_INVALID_REVNUM)
1852 if (!opt_state->quiet)
1853 feedback_stream = recode_stream_create(stdout, pool);
1855 verify_baton.keep_going = opt_state->keep_going;
1856 verify_baton.error_summary =
1857 apr_array_make(pool, 0, sizeof(struct verification_error *));
1858 verify_baton.result_pool = pool;
1860 SVN_ERR(svn_repos_verify_fs3(repos, lower, upper,
1861 opt_state->check_normalization,
1862 opt_state->metadata_only,
1864 ? repos_notify_handler : NULL,
1866 repos_verify_callback, &verify_baton,
1867 check_cancel, NULL, pool));
1869 /* Show the --keep-going error summary. */
1870 if (!opt_state->quiet
1871 && opt_state->keep_going
1872 && verify_baton.error_summary->nelts > 0)
1875 svn_revnum_t end_revnum;
1876 apr_pool_t *iterpool;
1880 svn_stream_puts(feedback_stream,
1881 _("\n-----Summary of corrupt revisions-----\n")));
1883 /* The standard column width for the revision number is 6 characters.
1884 If the revision number can potentially be larger (i.e. if end_revnum
1885 is larger than 1000000), we increase the column width as needed. */
1887 end_revnum = APR_ARRAY_IDX(verify_baton.error_summary,
1888 verify_baton.error_summary->nelts - 1,
1889 struct verification_error *)->rev;
1890 while (end_revnum >= 1000000)
1893 end_revnum = end_revnum / 10;
1896 iterpool = svn_pool_create(pool);
1897 for (i = 0; i < verify_baton.error_summary->nelts; i++)
1899 struct verification_error *verr;
1901 const char *rev_str;
1903 svn_pool_clear(iterpool);
1905 verr = APR_ARRAY_IDX(verify_baton.error_summary, i,
1906 struct verification_error *);
1908 if (verr->rev != SVN_INVALID_REVNUM)
1910 rev_str = apr_psprintf(iterpool, "r%ld", verr->rev);
1911 rev_str = apr_psprintf(iterpool, "%*s", rev_maxlength, rev_str);
1912 for (err = svn_error_purge_tracing(verr->err);
1913 err != SVN_NO_ERROR; err = err->child)
1916 const char *message;
1918 message = svn_err_best_message(err, buf, sizeof(buf));
1919 svn_error_clear(svn_stream_printf(feedback_stream, iterpool,
1921 rev_str, err->apr_err,
1927 svn_pool_destroy(iterpool);
1930 if (verify_baton.error_summary->nelts > 0)
1932 return svn_error_createf(SVN_ERR_CL_REPOS_VERIFY_FAILED, NULL,
1933 _("Failed to verify repository '%s'"),
1934 svn_dirent_local_style(
1935 opt_state->repository_path, pool));
1938 return SVN_NO_ERROR;
1941 /* This implements `svn_opt_subcommand_t'. */
1943 subcommand_hotcopy(apr_getopt_t *os, void *baton, apr_pool_t *pool)
1945 struct svnadmin_opt_state *opt_state = baton;
1946 svn_stream_t *feedback_stream = NULL;
1947 apr_array_header_t *targets;
1948 const char *new_repos_path;
1950 /* Expect one more argument: NEW_REPOS_PATH */
1951 SVN_ERR(parse_args(&targets, os, 1, 1, pool));
1952 new_repos_path = APR_ARRAY_IDX(targets, 0, const char *);
1953 SVN_ERR(target_arg_to_dirent(&new_repos_path, new_repos_path, pool));
1955 /* Progress feedback goes to STDOUT, unless they asked to suppress it. */
1956 if (! opt_state->quiet)
1957 feedback_stream = recode_stream_create(stdout, pool);
1959 return svn_repos_hotcopy3(opt_state->repository_path, new_repos_path,
1960 opt_state->clean_logs, opt_state->incremental,
1961 !opt_state->quiet ? repos_notify_handler : NULL,
1962 feedback_stream, check_cancel, NULL, pool);
1966 subcommand_info(apr_getopt_t *os, void *baton, apr_pool_t *pool)
1968 struct svnadmin_opt_state *opt_state = baton;
1974 /* Expect no more arguments. */
1975 SVN_ERR(parse_args(NULL, os, 0, 0, pool));
1977 SVN_ERR(open_repos(&repos, opt_state->repository_path, pool));
1978 fs = svn_repos_fs(repos);
1979 SVN_ERR(svn_cmdline_printf(pool, _("Path: %s\n"),
1980 svn_dirent_local_style(svn_repos_path(repos, pool),
1983 SVN_ERR(svn_fs_get_uuid(fs, &uuid, pool));
1984 SVN_ERR(svn_cmdline_printf(pool, _("UUID: %s\n"), uuid));
1986 int repos_format, minor;
1987 svn_version_t *repos_version, *fs_version;
1988 SVN_ERR(svn_repos_info_format(&repos_format, &repos_version,
1989 repos, pool, pool));
1990 SVN_ERR(svn_cmdline_printf(pool, _("Repository Format: %d\n"),
1993 SVN_ERR(svn_fs_info_format(&fs_format, &fs_version,
1995 /* fs_format will be printed later. */
1997 SVN_ERR_ASSERT(repos_version->major == SVN_VER_MAJOR);
1998 SVN_ERR_ASSERT(fs_version->major == SVN_VER_MAJOR);
1999 SVN_ERR_ASSERT(repos_version->patch == 0);
2000 SVN_ERR_ASSERT(fs_version->patch == 0);
2002 minor = (repos_version->minor > fs_version->minor)
2003 ? repos_version->minor : fs_version->minor;
2004 SVN_ERR(svn_cmdline_printf(pool, _("Compatible With Version: %d.%d.0\n"),
2005 SVN_VER_MAJOR, minor));
2009 apr_hash_t *capabilities_set;
2010 apr_array_header_t *capabilities;
2013 SVN_ERR(svn_repos_capabilities(&capabilities_set, repos, pool, pool));
2014 capabilities = svn_sort__hash(capabilities_set,
2015 svn_sort_compare_items_lexically,
2018 for (i = 0; i < capabilities->nelts; i++)
2020 svn_sort__item_t *item = &APR_ARRAY_IDX(capabilities, i,
2022 const char *capability = item->key;
2023 SVN_ERR(svn_cmdline_printf(pool, _("Repository Capability: %s\n"),
2029 const svn_fs_info_placeholder_t *info;
2031 SVN_ERR(svn_fs_info(&info, fs, pool, pool));
2032 SVN_ERR(svn_cmdline_printf(pool, _("Filesystem Type: %s\n"),
2034 SVN_ERR(svn_cmdline_printf(pool, _("Filesystem Format: %d\n"),
2036 if (!strcmp(info->fs_type, SVN_FS_TYPE_FSFS))
2038 const svn_fs_fsfs_info_t *fsfs_info = (const void *)info;
2039 svn_revnum_t youngest;
2040 SVN_ERR(svn_fs_youngest_rev(&youngest, fs, pool));
2042 if (fsfs_info->shard_size)
2043 SVN_ERR(svn_cmdline_printf(pool, _("FSFS Sharded: yes\n")));
2045 SVN_ERR(svn_cmdline_printf(pool, _("FSFS Sharded: no\n")));
2047 if (fsfs_info->shard_size)
2048 SVN_ERR(svn_cmdline_printf(pool, _("FSFS Shard Size: %d\n"),
2049 fsfs_info->shard_size));
2051 /* Print packing statistics, if enabled on the FS. */
2052 if (fsfs_info->shard_size)
2054 const int shard_size = fsfs_info->shard_size;
2055 const long shards_packed = fsfs_info->min_unpacked_rev / shard_size;
2056 const long shards_full = (youngest + 1) / shard_size;
2057 SVN_ERR(svn_cmdline_printf(pool, _("FSFS Shards Packed: %ld/%ld\n"),
2058 shards_packed, shards_full));
2061 if (fsfs_info->log_addressing)
2062 SVN_ERR(svn_cmdline_printf(pool, _("FSFS Logical Addressing: yes\n")));
2064 SVN_ERR(svn_cmdline_printf(pool, _("FSFS Logical Addressing: no\n")));
2069 apr_array_header_t *files;
2072 SVN_ERR(svn_fs_info_config_files(&files, fs, pool, pool));
2073 for (i = 0; i < files->nelts; i++)
2074 SVN_ERR(svn_cmdline_printf(pool, _("Configuration File: %s\n"),
2075 svn_dirent_local_style(
2076 APR_ARRAY_IDX(files, i, const char *),
2080 /* 'svn info' prints an extra newline here, to support multiple targets.
2081 We'll do the same. */
2082 SVN_ERR(svn_cmdline_printf(pool, "\n"));
2084 return SVN_NO_ERROR;
2087 /* This implements `svn_opt_subcommand_t'. */
2088 static svn_error_t *
2089 subcommand_lock(apr_getopt_t *os, void *baton, apr_pool_t *pool)
2091 struct svnadmin_opt_state *opt_state = baton;
2094 svn_fs_access_t *access;
2095 apr_array_header_t *args;
2096 const char *username;
2097 const char *lock_path;
2098 const char *comment_file_name;
2099 svn_stringbuf_t *file_contents;
2100 const char *lock_path_utf8;
2102 const char *lock_token = NULL;
2104 /* Expect three more arguments: PATH USERNAME COMMENT-FILE */
2105 SVN_ERR(parse_args(&args, os, 3, 4, pool));
2106 lock_path = APR_ARRAY_IDX(args, 0, const char *);
2107 username = APR_ARRAY_IDX(args, 1, const char *);
2108 comment_file_name = APR_ARRAY_IDX(args, 2, const char *);
2110 /* Expect one more optional argument: TOKEN */
2111 if (args->nelts == 4)
2112 lock_token = APR_ARRAY_IDX(args, 3, const char *);
2114 SVN_ERR(target_arg_to_dirent(&comment_file_name, comment_file_name, pool));
2116 SVN_ERR(open_repos(&repos, opt_state->repository_path, pool));
2117 fs = svn_repos_fs(repos);
2119 /* Create an access context describing the user. */
2120 SVN_ERR(svn_fs_create_access(&access, username, pool));
2122 /* Attach the access context to the filesystem. */
2123 SVN_ERR(svn_fs_set_access(fs, access));
2125 SVN_ERR(svn_stringbuf_from_file2(&file_contents, comment_file_name, pool));
2127 SVN_ERR(svn_utf_cstring_to_utf8(&lock_path_utf8, lock_path, pool));
2129 if (opt_state->bypass_hooks)
2130 SVN_ERR(svn_fs_lock(&lock, fs, lock_path_utf8,
2132 file_contents->data, /* comment */
2133 0, /* is_dav_comment */
2134 0, /* no expiration time. */
2138 SVN_ERR(svn_repos_fs_lock(&lock, repos, lock_path_utf8,
2140 file_contents->data, /* comment */
2141 0, /* is_dav_comment */
2142 0, /* no expiration time. */
2146 SVN_ERR(svn_cmdline_printf(pool, _("'%s' locked by user '%s'.\n"),
2147 lock_path, username));
2148 return SVN_NO_ERROR;
2151 static svn_error_t *
2152 subcommand_lslocks(apr_getopt_t *os, void *baton, apr_pool_t *pool)
2154 struct svnadmin_opt_state *opt_state = baton;
2155 apr_array_header_t *targets;
2157 const char *fs_path = "/";
2159 apr_hash_index_t *hi;
2160 apr_pool_t *iterpool = svn_pool_create(pool);
2162 SVN_ERR(svn_opt__args_to_target_array(&targets, os,
2163 apr_array_make(pool, 0,
2164 sizeof(const char *)),
2166 if (targets->nelts > 1)
2167 return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, 0,
2168 _("Too many arguments given"));
2170 fs_path = APR_ARRAY_IDX(targets, 0, const char *);
2172 SVN_ERR(open_repos(&repos, opt_state->repository_path, pool));
2174 /* Fetch all locks on or below the root directory. */
2175 SVN_ERR(svn_repos_fs_get_locks2(&locks, repos, fs_path, svn_depth_infinity,
2178 for (hi = apr_hash_first(pool, locks); hi; hi = apr_hash_next(hi))
2180 const char *cr_date, *exp_date = "";
2181 const char *path = apr_hash_this_key(hi);
2182 svn_lock_t *lock = apr_hash_this_val(hi);
2183 int comment_lines = 0;
2185 svn_pool_clear(iterpool);
2187 SVN_ERR(check_cancel(NULL));
2189 cr_date = svn_time_to_human_cstring(lock->creation_date, iterpool);
2191 if (lock->expiration_date)
2192 exp_date = svn_time_to_human_cstring(lock->expiration_date, iterpool);
2195 comment_lines = svn_cstring_count_newlines(lock->comment) + 1;
2197 SVN_ERR(svn_cmdline_printf(iterpool, _("Path: %s\n"), path));
2198 SVN_ERR(svn_cmdline_printf(iterpool, _("UUID Token: %s\n"), lock->token));
2199 SVN_ERR(svn_cmdline_printf(iterpool, _("Owner: %s\n"), lock->owner));
2200 SVN_ERR(svn_cmdline_printf(iterpool, _("Created: %s\n"), cr_date));
2201 SVN_ERR(svn_cmdline_printf(iterpool, _("Expires: %s\n"), exp_date));
2202 SVN_ERR(svn_cmdline_printf(iterpool,
2203 Q_("Comment (%i line):\n%s\n\n",
2204 "Comment (%i lines):\n%s\n\n",
2207 lock->comment ? lock->comment : ""));
2210 svn_pool_destroy(iterpool);
2212 return SVN_NO_ERROR;
2217 static svn_error_t *
2218 subcommand_rmlocks(apr_getopt_t *os, void *baton, apr_pool_t *pool)
2220 struct svnadmin_opt_state *opt_state = baton;
2223 svn_fs_access_t *access;
2225 apr_array_header_t *args;
2227 const char *username;
2228 apr_pool_t *subpool = svn_pool_create(pool);
2230 SVN_ERR(open_repos(&repos, opt_state->repository_path, pool));
2231 fs = svn_repos_fs(repos);
2233 /* svn_fs_unlock() demands that some username be associated with the
2234 filesystem, so just use the UID of the person running 'svnadmin'.*/
2235 username = svn_user_get_name(pool);
2237 username = "administrator";
2239 /* Create an access context describing the current user. */
2240 SVN_ERR(svn_fs_create_access(&access, username, pool));
2242 /* Attach the access context to the filesystem. */
2243 SVN_ERR(svn_fs_set_access(fs, access));
2245 /* Parse out any options. */
2246 SVN_ERR(svn_opt_parse_all_args(&args, os, pool));
2248 /* Our usage requires at least one FS path. */
2249 if (args->nelts == 0)
2250 return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, 0,
2251 _("No paths to unlock provided"));
2253 /* All the rest of the arguments are paths from which to remove locks. */
2254 for (i = 0; i < args->nelts; i++)
2256 const char *lock_path = APR_ARRAY_IDX(args, i, const char *);
2257 const char *lock_path_utf8;
2260 SVN_ERR(svn_utf_cstring_to_utf8(&lock_path_utf8, lock_path, subpool));
2262 /* Fetch the path's svn_lock_t. */
2263 err = svn_fs_get_lock(&lock, fs, lock_path_utf8, subpool);
2268 SVN_ERR(svn_cmdline_printf(subpool,
2269 _("Path '%s' isn't locked.\n"),
2274 /* Now forcibly destroy the lock. */
2275 err = svn_fs_unlock(fs, lock_path_utf8,
2276 lock->token, 1 /* force */, subpool);
2280 SVN_ERR(svn_cmdline_printf(subpool,
2281 _("Removed lock on '%s'.\n"), lock->path));
2286 /* Print the error, but move on to the next lock. */
2287 svn_handle_error2(err, stderr, FALSE /* non-fatal */, "svnadmin: ");
2288 svn_error_clear(err);
2291 svn_pool_clear(subpool);
2294 svn_pool_destroy(subpool);
2295 return SVN_NO_ERROR;
2299 /* This implements `svn_opt_subcommand_t'. */
2300 static svn_error_t *
2301 subcommand_unlock(apr_getopt_t *os, void *baton, apr_pool_t *pool)
2303 struct svnadmin_opt_state *opt_state = baton;
2306 svn_fs_access_t *access;
2307 apr_array_header_t *args;
2308 const char *username;
2309 const char *lock_path;
2310 const char *lock_path_utf8;
2311 const char *lock_token = NULL;
2313 /* Expect three more arguments: PATH USERNAME TOKEN */
2314 SVN_ERR(parse_args(&args, os, 3, 3, pool));
2315 lock_path = APR_ARRAY_IDX(args, 0, const char *);
2316 username = APR_ARRAY_IDX(args, 1, const char *);
2317 lock_token = APR_ARRAY_IDX(args, 2, const char *);
2319 /* Open the repos/FS, and associate an access context containing
2321 SVN_ERR(open_repos(&repos, opt_state->repository_path, pool));
2322 fs = svn_repos_fs(repos);
2323 SVN_ERR(svn_fs_create_access(&access, username, pool));
2324 SVN_ERR(svn_fs_set_access(fs, access));
2326 SVN_ERR(svn_utf_cstring_to_utf8(&lock_path_utf8, lock_path, pool));
2327 if (opt_state->bypass_hooks)
2328 SVN_ERR(svn_fs_unlock(fs, lock_path_utf8, lock_token,
2331 SVN_ERR(svn_repos_fs_unlock(repos, lock_path_utf8, lock_token,
2334 SVN_ERR(svn_cmdline_printf(pool, _("'%s' unlocked by user '%s'.\n"),
2335 lock_path, username));
2336 return SVN_NO_ERROR;
2340 /* This implements `svn_opt_subcommand_t'. */
2341 static svn_error_t *
2342 subcommand_upgrade(apr_getopt_t *os, void *baton, apr_pool_t *pool)
2345 struct svnadmin_opt_state *opt_state = baton;
2346 svn_stream_t *feedback_stream = NULL;
2348 /* Expect no more arguments. */
2349 SVN_ERR(parse_args(NULL, os, 0, 0, pool));
2351 SVN_ERR(svn_stream_for_stdout(&feedback_stream, pool));
2353 /* Restore default signal handlers. */
2354 setup_cancellation_signals(SIG_DFL);
2356 err = svn_repos_upgrade2(opt_state->repository_path, TRUE,
2357 repos_notify_handler, feedback_stream, pool);
2360 if (APR_STATUS_IS_EAGAIN(err->apr_err))
2362 svn_error_clear(err);
2364 if (! opt_state->wait)
2365 return svn_error_create(SVN_ERR_REPOS_LOCKED, NULL,
2366 _("Failed to get exclusive repository "
2367 "access; perhaps another process\n"
2368 "such as httpd, svnserve or svn "
2370 SVN_ERR(svn_cmdline_printf(pool,
2371 _("Waiting on repository lock; perhaps"
2372 " another process has it open?\n")));
2373 SVN_ERR(svn_cmdline_fflush(stdout));
2374 SVN_ERR(svn_repos_upgrade2(opt_state->repository_path, FALSE,
2375 repos_notify_handler, feedback_stream,
2378 else if (err->apr_err == SVN_ERR_FS_UNSUPPORTED_UPGRADE)
2380 return svn_error_quick_wrap(err,
2381 _("Upgrade of this repository's underlying versioned "
2382 "filesystem is not supported; consider "
2383 "dumping and loading the data elsewhere"));
2385 else if (err->apr_err == SVN_ERR_REPOS_UNSUPPORTED_UPGRADE)
2387 return svn_error_quick_wrap(err,
2388 _("Upgrade of this repository is not supported; consider "
2389 "dumping and loading the data elsewhere"));
2394 SVN_ERR(svn_cmdline_printf(pool, _("\nUpgrade completed.\n")));
2395 return SVN_NO_ERROR;
2399 /* This implements `svn_opt_subcommand_t'. */
2400 static svn_error_t *
2401 subcommand_delrevprop(apr_getopt_t *os, void *baton, apr_pool_t *pool)
2403 struct svnadmin_opt_state *opt_state = baton;
2404 apr_array_header_t *args;
2405 const char *prop_name;
2407 /* Expect one more argument: NAME */
2408 SVN_ERR(parse_args(&args, os, 1, 1, pool));
2409 prop_name = APR_ARRAY_IDX(args, 0, const char *);
2411 if (opt_state->txn_id)
2413 if (opt_state->start_revision.kind != svn_opt_revision_unspecified
2414 || opt_state->end_revision.kind != svn_opt_revision_unspecified)
2415 return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
2416 _("--revision (-r) and --transaction (-t) "
2417 "are mutually exclusive"));
2419 if (opt_state->use_pre_revprop_change_hook
2420 || opt_state->use_post_revprop_change_hook)
2421 return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
2422 _("Calling hooks is incompatible with "
2423 "--transaction (-t)"));
2425 else if (opt_state->start_revision.kind != svn_opt_revision_number)
2426 return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
2427 _("Missing revision"));
2428 else if (opt_state->end_revision.kind != svn_opt_revision_unspecified)
2429 return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
2430 _("Only one revision allowed"));
2432 return set_revprop(prop_name, NULL, opt_state, pool);
2440 * On success, leave *EXIT_CODE untouched and return SVN_NO_ERROR. On error,
2441 * either return an error to be displayed, or set *EXIT_CODE to non-zero and
2442 * return SVN_NO_ERROR.
2444 static svn_error_t *
2445 sub_main(int *exit_code, int argc, const char *argv[], apr_pool_t *pool)
2448 apr_status_t apr_err;
2450 const svn_opt_subcommand_desc2_t *subcommand = NULL;
2451 struct svnadmin_opt_state opt_state = { 0 };
2454 apr_array_header_t *received_opts;
2456 svn_boolean_t dash_F_arg = FALSE;
2458 received_opts = apr_array_make(pool, SVN_OPT_MAX_OPTIONS, sizeof(int));
2460 /* Check library versions */
2461 SVN_ERR(check_lib_versions());
2463 /* Initialize the FS library. */
2464 SVN_ERR(svn_fs_initialize(pool));
2468 SVN_ERR(subcommand_help(NULL, NULL, pool));
2469 *exit_code = EXIT_FAILURE;
2470 return SVN_NO_ERROR;
2473 /* Initialize opt_state. */
2474 opt_state.start_revision.kind = svn_opt_revision_unspecified;
2475 opt_state.end_revision.kind = svn_opt_revision_unspecified;
2476 opt_state.memory_cache_size = svn_cache_config_get()->cache_size;
2478 /* Parse options. */
2479 SVN_ERR(svn_cmdline__getopt_init(&os, argc, argv, pool));
2485 const char *opt_arg;
2486 const char *utf8_opt_arg;
2488 /* Parse the next option. */
2489 apr_err = apr_getopt_long(os, options_table, &opt_id, &opt_arg);
2490 if (APR_STATUS_IS_EOF(apr_err))
2494 SVN_ERR(subcommand_help(NULL, NULL, pool));
2495 *exit_code = EXIT_FAILURE;
2496 return SVN_NO_ERROR;
2499 /* Stash the option code in an array before parsing it. */
2500 APR_ARRAY_PUSH(received_opts, int) = opt_id;
2505 if (opt_state.start_revision.kind != svn_opt_revision_unspecified)
2507 return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
2508 _("Multiple revision arguments encountered; "
2509 "try '-r N:M' instead of '-r N -r M'"));
2511 if (svn_opt_parse_revision(&(opt_state.start_revision),
2512 &(opt_state.end_revision),
2513 opt_arg, pool) != 0)
2515 SVN_ERR(svn_utf_cstring_to_utf8(&utf8_opt_arg, opt_arg, pool));
2517 return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
2518 _("Syntax error in revision argument '%s'"),
2524 opt_state.txn_id = opt_arg;
2528 opt_state.quiet = TRUE;
2532 opt_state.help = TRUE;
2535 opt_state.memory_cache_size
2536 = 0x100000 * apr_strtoi64(opt_arg, NULL, 0);
2539 SVN_ERR(svn_utf_cstring_to_utf8(&utf8_opt_arg, opt_arg, pool));
2540 SVN_ERR(svn_stringbuf_from_file2(&(opt_state.filedata),
2541 utf8_opt_arg, pool));
2543 case svnadmin__version:
2544 opt_state.version = TRUE;
2546 case svnadmin__incremental:
2547 opt_state.incremental = TRUE;
2549 case svnadmin__deltas:
2550 opt_state.use_deltas = TRUE;
2552 case svnadmin__ignore_uuid:
2553 opt_state.uuid_action = svn_repos_load_uuid_ignore;
2555 case svnadmin__force_uuid:
2556 opt_state.uuid_action = svn_repos_load_uuid_force;
2558 case svnadmin__pre_1_4_compatible:
2559 opt_state.compatible_version = apr_pcalloc(pool, sizeof(svn_version_t));
2560 opt_state.compatible_version->major = 1;
2561 opt_state.compatible_version->minor = 3;
2563 case svnadmin__pre_1_5_compatible:
2564 opt_state.compatible_version = apr_pcalloc(pool, sizeof(svn_version_t));
2565 opt_state.compatible_version->major = 1;
2566 opt_state.compatible_version->minor = 4;
2568 case svnadmin__pre_1_6_compatible:
2569 opt_state.compatible_version = apr_pcalloc(pool, sizeof(svn_version_t));
2570 opt_state.compatible_version->major = 1;
2571 opt_state.compatible_version->minor = 5;
2573 case svnadmin__compatible_version:
2575 svn_version_t latest = { SVN_VER_MAJOR, SVN_VER_MINOR,
2576 SVN_VER_PATCH, NULL };
2577 svn_version_t *compatible_version;
2579 /* Parse the version string which carries our target
2581 SVN_ERR(svn_version__parse_version_string(&compatible_version,
2584 /* We can't create repository with a version older than 1.0.0. */
2585 if (! svn_version__at_least(compatible_version, 1, 0, 0))
2587 return svn_error_createf(SVN_ERR_UNSUPPORTED_FEATURE, NULL,
2588 _("Cannot create pre-1.0-compatible "
2592 /* We can't create repository with a version newer than what
2593 the running version of Subversion supports. */
2594 if (! svn_version__at_least(&latest,
2595 compatible_version->major,
2596 compatible_version->minor,
2597 compatible_version->patch))
2599 return svn_error_createf(SVN_ERR_UNSUPPORTED_FEATURE, NULL,
2600 _("Cannot guarantee compatibility "
2601 "beyond the current running version "
2606 opt_state.compatible_version = compatible_version;
2609 case svnadmin__keep_going:
2610 opt_state.keep_going = TRUE;
2612 case svnadmin__check_normalization:
2613 opt_state.check_normalization = TRUE;
2615 case svnadmin__metadata_only:
2616 opt_state.metadata_only = TRUE;
2618 case svnadmin__fs_type:
2619 SVN_ERR(svn_utf_cstring_to_utf8(&opt_state.fs_type, opt_arg, pool));
2621 case svnadmin__parent_dir:
2622 SVN_ERR(svn_utf_cstring_to_utf8(&opt_state.parent_dir, opt_arg,
2624 opt_state.parent_dir
2625 = svn_dirent_internal_style(opt_state.parent_dir, pool);
2627 case svnadmin__use_pre_commit_hook:
2628 opt_state.use_pre_commit_hook = TRUE;
2630 case svnadmin__use_post_commit_hook:
2631 opt_state.use_post_commit_hook = TRUE;
2633 case svnadmin__use_pre_revprop_change_hook:
2634 opt_state.use_pre_revprop_change_hook = TRUE;
2636 case svnadmin__use_post_revprop_change_hook:
2637 opt_state.use_post_revprop_change_hook = TRUE;
2639 case svnadmin__bdb_txn_nosync:
2640 opt_state.bdb_txn_nosync = TRUE;
2642 case svnadmin__bdb_log_keep:
2643 opt_state.bdb_log_keep = TRUE;
2645 case svnadmin__bypass_hooks:
2646 opt_state.bypass_hooks = TRUE;
2648 case svnadmin__bypass_prop_validation:
2649 opt_state.bypass_prop_validation = TRUE;
2651 case svnadmin__ignore_dates:
2652 opt_state.ignore_dates = TRUE;
2654 case svnadmin__clean_logs:
2655 opt_state.clean_logs = TRUE;
2657 case svnadmin__config_dir:
2658 SVN_ERR(svn_utf_cstring_to_utf8(&utf8_opt_arg, opt_arg, pool));
2659 opt_state.config_dir =
2660 apr_pstrdup(pool, svn_dirent_canonicalize(utf8_opt_arg, pool));
2662 case svnadmin__wait:
2663 opt_state.wait = TRUE;
2667 SVN_ERR(subcommand_help(NULL, NULL, pool));
2668 *exit_code = EXIT_FAILURE;
2669 return SVN_NO_ERROR;
2671 } /* close `switch' */
2672 } /* close `while' */
2674 /* If the user asked for help, then the rest of the arguments are
2675 the names of subcommands to get help on (if any), or else they're
2676 just typos/mistakes. Whatever the case, the subcommand to
2677 actually run is subcommand_help(). */
2679 subcommand = svn_opt_get_canonical_subcommand2(cmd_table, "help");
2681 /* If we're not running the `help' subcommand, then look for a
2682 subcommand in the first argument. */
2683 if (subcommand == NULL)
2685 if (os->ind >= os->argc)
2687 if (opt_state.version)
2689 /* Use the "help" subcommand to handle the "--version" option. */
2690 static const svn_opt_subcommand_desc2_t pseudo_cmd =
2691 { "--version", subcommand_help, {0}, "",
2692 {svnadmin__version, /* must accept its own option */
2696 subcommand = &pseudo_cmd;
2700 svn_error_clear(svn_cmdline_fprintf(stderr, pool,
2701 _("subcommand argument required\n")));
2702 SVN_ERR(subcommand_help(NULL, NULL, pool));
2703 *exit_code = EXIT_FAILURE;
2704 return SVN_NO_ERROR;
2709 const char *first_arg = os->argv[os->ind++];
2710 subcommand = svn_opt_get_canonical_subcommand2(cmd_table, first_arg);
2711 if (subcommand == NULL)
2713 const char *first_arg_utf8;
2714 SVN_ERR(svn_utf_cstring_to_utf8(&first_arg_utf8,
2717 svn_cmdline_fprintf(stderr, pool,
2718 _("Unknown subcommand: '%s'\n"),
2720 SVN_ERR(subcommand_help(NULL, NULL, pool));
2721 *exit_code = EXIT_FAILURE;
2722 return SVN_NO_ERROR;
2727 /* Every subcommand except `help' and `freeze' with '-F' require a
2728 second argument -- the repository path. Parse it out here and
2729 store it in opt_state. */
2730 if (!(subcommand->cmd_func == subcommand_help
2731 || (subcommand->cmd_func == subcommand_freeze && dash_F_arg)))
2733 const char *repos_path = NULL;
2735 if (os->ind >= os->argc)
2737 return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
2738 _("Repository argument required"));
2741 SVN_ERR(svn_utf_cstring_to_utf8(&repos_path, os->argv[os->ind++], pool));
2743 if (svn_path_is_url(repos_path))
2745 return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
2746 _("'%s' is a URL when it should be a "
2747 "local path"), repos_path);
2750 opt_state.repository_path = svn_dirent_internal_style(repos_path, pool);
2753 /* Check that the subcommand wasn't passed any inappropriate options. */
2754 for (i = 0; i < received_opts->nelts; i++)
2756 opt_id = APR_ARRAY_IDX(received_opts, i, int);
2758 /* All commands implicitly accept --help, so just skip over this
2759 when we see it. Note that we don't want to include this option
2760 in their "accepted options" list because it would be awfully
2761 redundant to display it in every commands' help text. */
2762 if (opt_id == 'h' || opt_id == '?')
2765 if (! svn_opt_subcommand_takes_option3(subcommand, opt_id, NULL))
2768 const apr_getopt_option_t *badopt =
2769 svn_opt_get_option_from_code2(opt_id, options_table, subcommand,
2771 svn_opt_format_option(&optstr, badopt, FALSE, pool);
2772 if (subcommand->name[0] == '-')
2773 SVN_ERR(subcommand_help(NULL, NULL, pool));
2775 svn_error_clear(svn_cmdline_fprintf(stderr, pool
2776 , _("Subcommand '%s' doesn't accept option '%s'\n"
2777 "Type 'svnadmin help %s' for usage.\n"),
2778 subcommand->name, optstr, subcommand->name));
2779 *exit_code = EXIT_FAILURE;
2780 return SVN_NO_ERROR;
2784 /* Set up our cancellation support. */
2785 setup_cancellation_signals(signal_handler);
2788 /* Disable SIGPIPE generation for the platforms that have it. */
2789 apr_signal(SIGPIPE, SIG_IGN);
2793 /* Disable SIGXFSZ generation for the platforms that have it, otherwise
2794 * working with large files when compiled against an APR that doesn't have
2795 * large file support will crash the program, which is uncool. */
2796 apr_signal(SIGXFSZ, SIG_IGN);
2799 /* Configure FSFS caches for maximum efficiency with svnadmin.
2800 * Also, apply the respective command line parameters, if given. */
2802 svn_cache_config_t settings = *svn_cache_config_get();
2804 settings.cache_size = opt_state.memory_cache_size;
2805 settings.single_threaded = TRUE;
2807 svn_cache_config_set(&settings);
2810 /* Run the subcommand. */
2811 err = (*subcommand->cmd_func)(os, &opt_state, pool);
2814 /* For argument-related problems, suggest using the 'help'
2816 if (err->apr_err == SVN_ERR_CL_INSUFFICIENT_ARGS
2817 || err->apr_err == SVN_ERR_CL_ARG_PARSING_ERROR)
2819 err = svn_error_quick_wrap(err,
2820 _("Try 'svnadmin help' for more info"));
2825 return SVN_NO_ERROR;
2829 main(int argc, const char *argv[])
2832 int exit_code = EXIT_SUCCESS;
2835 /* Initialize the app. */
2836 if (svn_cmdline_init("svnadmin", stderr) != EXIT_SUCCESS)
2837 return EXIT_FAILURE;
2839 /* Create our top-level pool. Use a separate mutexless allocator,
2840 * given this application is single threaded.
2842 pool = apr_allocator_owner_get(svn_pool_create_allocator(FALSE));
2844 err = sub_main(&exit_code, argc, argv, pool);
2846 /* Flush stdout and report if it fails. It would be flushed on exit anyway
2847 but this makes sure that output is not silently lost if it fails. */
2848 err = svn_error_compose_create(err, svn_cmdline_fflush(stdout));
2852 exit_code = EXIT_FAILURE;
2853 svn_cmdline_handle_exit_error(err, NULL, "svnadmin: ");
2856 svn_pool_destroy(pool);