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"
400 "2. Like 1 except all repositories listed in FILE are locked. The file\n"
401 " format is repository paths separated by newlines. Repositories are\n"
402 " locked in the same order as they are listed in the file.\n"),
405 {"help", subcommand_help, {"?", "h"}, N_
406 ("usage: svnadmin help [SUBCOMMAND...]\n\n"
407 "Describe the usage of this program or its subcommands.\n"),
410 {"hotcopy", subcommand_hotcopy, {0}, N_
411 ("usage: svnadmin hotcopy REPOS_PATH NEW_REPOS_PATH\n\n"
412 "Make a hot copy of a repository.\n"
413 "If --incremental is passed, data which already exists at the destination\n"
414 "is not copied again. Incremental mode is implemented for FSFS repositories.\n"),
415 {svnadmin__clean_logs, svnadmin__incremental, 'q'} },
417 {"info", subcommand_info, {0}, N_
418 ("usage: svnadmin info REPOS_PATH\n\n"
419 "Print information about the repository at REPOS_PATH.\n"),
422 {"list-dblogs", subcommand_list_dblogs, {0}, N_
423 ("usage: svnadmin list-dblogs REPOS_PATH\n\n"
424 "List all Berkeley DB log files.\n\n"
425 "WARNING: Modifying or deleting logfiles which are still in use\n"
426 "will cause your repository to be corrupted.\n"),
429 {"list-unused-dblogs", subcommand_list_unused_dblogs, {0}, N_
430 ("usage: svnadmin list-unused-dblogs REPOS_PATH\n\n"
431 "List unused Berkeley DB log files.\n\n"),
434 {"load", subcommand_load, {0}, N_
435 ("usage: svnadmin load REPOS_PATH\n\n"
436 "Read a 'dumpfile'-formatted stream from stdin, committing\n"
437 "new revisions into the repository's filesystem. If the repository\n"
438 "was previously empty, its UUID will, by default, be changed to the\n"
439 "one specified in the stream. Progress feedback is sent to stdout.\n"
440 "If --revision is specified, limit the loaded revisions to only those\n"
441 "in the dump stream whose revision numbers match the specified range.\n"),
442 {'q', 'r', svnadmin__ignore_uuid, svnadmin__force_uuid,
443 svnadmin__ignore_dates,
444 svnadmin__use_pre_commit_hook, svnadmin__use_post_commit_hook,
445 svnadmin__parent_dir, svnadmin__bypass_prop_validation, 'M'} },
447 {"lock", subcommand_lock, {0}, N_
448 ("usage: svnadmin lock REPOS_PATH PATH USERNAME COMMENT-FILE [TOKEN]\n\n"
449 "Lock PATH by USERNAME setting comments from COMMENT-FILE.\n"
450 "If provided, use TOKEN as lock token. Use --bypass-hooks to avoid\n"
451 "triggering the pre-lock and post-lock hook scripts.\n"),
452 {svnadmin__bypass_hooks} },
454 {"lslocks", subcommand_lslocks, {0}, N_
455 ("usage: svnadmin lslocks REPOS_PATH [PATH-IN-REPOS]\n\n"
456 "Print descriptions of all locks on or under PATH-IN-REPOS (which,\n"
457 "if not provided, is the root of the repository).\n"),
460 {"lstxns", subcommand_lstxns, {0}, N_
461 ("usage: svnadmin lstxns REPOS_PATH\n\n"
462 "Print the names of all uncommitted transactions.\n"),
465 {"pack", subcommand_pack, {0}, N_
466 ("usage: svnadmin pack REPOS_PATH\n\n"
467 "Possibly compact the repository into a more efficient storage model.\n"
468 "This may not apply to all repositories, in which case, exit.\n"),
471 {"recover", subcommand_recover, {0}, N_
472 ("usage: svnadmin recover REPOS_PATH\n\n"
473 "Run the recovery procedure on a repository. Do this if you've\n"
474 "been getting errors indicating that recovery ought to be run.\n"
475 "Berkeley DB recovery requires exclusive access and will\n"
476 "exit if the repository is in use by another process.\n"),
479 {"rmlocks", subcommand_rmlocks, {0}, N_
480 ("usage: svnadmin rmlocks REPOS_PATH LOCKED_PATH...\n\n"
481 "Unconditionally remove lock from each LOCKED_PATH.\n"),
484 {"rmtxns", subcommand_rmtxns, {0}, N_
485 ("usage: svnadmin rmtxns REPOS_PATH TXN_NAME...\n\n"
486 "Delete the named transaction(s).\n"),
489 {"setlog", subcommand_setlog, {0}, N_
490 ("usage: svnadmin setlog REPOS_PATH -r REVISION FILE\n\n"
491 "Set the log-message on revision REVISION to the contents of FILE. Use\n"
492 "--bypass-hooks to avoid triggering the revision-property-related hooks\n"
493 "(for example, if you do not want an email notification sent\n"
494 "from your post-revprop-change hook, or because the modification of\n"
495 "revision properties has not been enabled in the pre-revprop-change\n"
497 "NOTE: Revision properties are not versioned, so this command will\n"
498 "overwrite the previous log message.\n"),
499 {'r', svnadmin__bypass_hooks} },
501 {"setrevprop", subcommand_setrevprop, {0}, N_
502 ("usage: 1. svnadmin setrevprop REPOS_PATH -r REVISION NAME FILE\n"
503 " 2. svnadmin setrevprop REPOS_PATH -t TXN NAME FILE\n\n"
504 "1. Set the property NAME on revision REVISION to the contents of FILE.\n\n"
505 "Use --use-pre-revprop-change-hook/--use-post-revprop-change-hook to\n"
506 "trigger the revision property-related hooks (for example, if you want\n"
507 "an email notification sent from your post-revprop-change hook).\n\n"
508 "NOTE: Revision properties are not versioned, so this command will\n"
509 "overwrite the previous value of the property.\n\n"
510 "2. Set the property NAME on transaction TXN to the contents of FILE.\n"),
511 {'r', 't', svnadmin__use_pre_revprop_change_hook,
512 svnadmin__use_post_revprop_change_hook} },
514 {"setuuid", subcommand_setuuid, {0}, N_
515 ("usage: svnadmin setuuid REPOS_PATH [NEW_UUID]\n\n"
516 "Reset the repository UUID for the repository located at REPOS_PATH. If\n"
517 "NEW_UUID is provided, use that as the new repository UUID; otherwise,\n"
518 "generate a brand new UUID for the repository.\n"),
521 {"unlock", subcommand_unlock, {0}, N_
522 ("usage: svnadmin unlock REPOS_PATH LOCKED_PATH USERNAME TOKEN\n\n"
523 "Unlock LOCKED_PATH (as USERNAME) after verifying that the token\n"
524 "associated with the lock matches TOKEN. Use --bypass-hooks to avoid\n"
525 "triggering the pre-unlock and post-unlock hook scripts.\n"),
526 {svnadmin__bypass_hooks} },
528 {"upgrade", subcommand_upgrade, {0}, N_
529 ("usage: svnadmin upgrade REPOS_PATH\n\n"
530 "Upgrade the repository located at REPOS_PATH to the latest supported\n"
531 "schema version.\n\n"
532 "This functionality is provided as a convenience for repository\n"
533 "administrators who wish to make use of new Subversion functionality\n"
534 "without having to undertake a potentially costly full repository dump\n"
535 "and load operation. As such, the upgrade performs only the minimum\n"
536 "amount of work needed to accomplish this while still maintaining the\n"
537 "integrity of the repository. It does not guarantee the most optimized\n"
538 "repository state as a dump and subsequent load would.\n"),
541 {"verify", subcommand_verify, {0}, N_
542 ("usage: svnadmin verify REPOS_PATH\n\n"
543 "Verify the data stored in the repository.\n"),
544 {'t', 'r', 'q', svnadmin__keep_going, 'M',
545 svnadmin__check_normalization, svnadmin__metadata_only} },
547 { NULL, NULL, {0}, NULL, {0} }
551 /* Baton for passing option/argument state to a subcommand function. */
552 struct svnadmin_opt_state
554 const char *repository_path;
555 const char *fs_type; /* --fs-type */
556 svn_version_t *compatible_version; /* --compatible-version */
557 svn_opt_revision_t start_revision, end_revision; /* -r X[:Y] */
558 const char *txn_id; /* -t TXN */
559 svn_boolean_t help; /* --help or -? */
560 svn_boolean_t version; /* --version */
561 svn_boolean_t incremental; /* --incremental */
562 svn_boolean_t use_deltas; /* --deltas */
563 svn_boolean_t use_pre_commit_hook; /* --use-pre-commit-hook */
564 svn_boolean_t use_post_commit_hook; /* --use-post-commit-hook */
565 svn_boolean_t use_pre_revprop_change_hook; /* --use-pre-revprop-change-hook */
566 svn_boolean_t use_post_revprop_change_hook; /* --use-post-revprop-change-hook */
567 svn_boolean_t quiet; /* --quiet */
568 svn_boolean_t bdb_txn_nosync; /* --bdb-txn-nosync */
569 svn_boolean_t bdb_log_keep; /* --bdb-log-keep */
570 svn_boolean_t clean_logs; /* --clean-logs */
571 svn_boolean_t bypass_hooks; /* --bypass-hooks */
572 svn_boolean_t wait; /* --wait */
573 svn_boolean_t keep_going; /* --keep-going */
574 svn_boolean_t check_normalization; /* --check-normalization */
575 svn_boolean_t metadata_only; /* --metadata-only */
576 svn_boolean_t bypass_prop_validation; /* --bypass-prop-validation */
577 svn_boolean_t ignore_dates; /* --ignore-dates */
578 enum svn_repos_load_uuid uuid_action; /* --ignore-uuid,
580 apr_uint64_t memory_cache_size; /* --memory-cache-size M */
581 const char *parent_dir; /* --parent-dir */
582 svn_stringbuf_t *filedata; /* --file */
584 const char *config_dir; /* Overriding Configuration Directory */
588 /* Set *REVNUM to the revision specified by REVISION (or to
589 SVN_INVALID_REVNUM if that has the type 'unspecified'),
590 possibly making use of the YOUNGEST revision number in REPOS. */
592 get_revnum(svn_revnum_t *revnum, const svn_opt_revision_t *revision,
593 svn_revnum_t youngest, svn_repos_t *repos, apr_pool_t *pool)
595 if (revision->kind == svn_opt_revision_number)
596 *revnum = revision->value.number;
597 else if (revision->kind == svn_opt_revision_head)
599 else if (revision->kind == svn_opt_revision_date)
600 SVN_ERR(svn_repos_dated_revision(revnum, repos, revision->value.date,
602 else if (revision->kind == svn_opt_revision_unspecified)
603 *revnum = SVN_INVALID_REVNUM;
605 return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
606 _("Invalid revision specifier"));
608 if (*revnum > youngest)
609 return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
610 _("Revisions must not be greater than the youngest revision (%ld)"),
616 /* Set *PATH to an internal-style, UTF8-encoded, local dirent path
617 allocated from POOL and parsed from raw command-line argument ARG. */
619 target_arg_to_dirent(const char **dirent,
625 SVN_ERR(svn_utf_cstring_to_utf8(&path, arg, pool));
626 if (svn_path_is_url(path))
627 return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
628 _("Path '%s' is not a local path"), path);
629 *dirent = svn_dirent_internal_style(path, pool);
633 /* Parse the remaining command-line arguments from OS, returning them
634 in a new array *ARGS (allocated from POOL) and optionally verifying
635 that we got the expected number thereof. If MIN_EXPECTED is not
636 negative, return an error if the function would return fewer than
637 MIN_EXPECTED arguments. If MAX_EXPECTED is not negative, return an
638 error if the function would return more than MAX_EXPECTED
641 As a special case, when MIN_EXPECTED and MAX_EXPECTED are both 0,
642 allow ARGS to be NULL. */
644 parse_args(apr_array_header_t **args,
650 int num_args = os ? (os->argc - os->ind) : 0;
652 if (min_expected || max_expected)
653 SVN_ERR_ASSERT(args);
655 if ((min_expected >= 0) && (num_args < min_expected))
656 return svn_error_create(SVN_ERR_CL_INSUFFICIENT_ARGS, 0,
657 _("Not enough arguments"));
658 if ((max_expected >= 0) && (num_args > max_expected))
659 return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, 0,
660 _("Too many arguments"));
663 *args = apr_array_make(pool, num_args, sizeof(const char *));
666 while (os->ind < os->argc)
667 APR_ARRAY_PUSH(*args, const char *) =
668 apr_pstrdup(pool, os->argv[os->ind++]);
675 /* This implements 'svn_error_malfunction_handler_t. */
677 crashtest_malfunction_handler(svn_boolean_t can_return,
683 return SVN_NO_ERROR; /* Not reached. */
686 /* This implements `svn_opt_subcommand_t'. */
688 subcommand_crashtest(apr_getopt_t *os, void *baton, apr_pool_t *pool)
690 struct svnadmin_opt_state *opt_state = baton;
693 (void)svn_error_set_malfunction_handler(crashtest_malfunction_handler);
694 SVN_ERR(open_repos(&repos, opt_state->repository_path, pool));
695 SVN_ERR(svn_cmdline_printf(pool,
696 _("Successfully opened repository '%s'.\n"
697 "Will now crash to simulate a crashing "
698 "server process.\n"),
699 svn_dirent_local_style(opt_state->repository_path,
701 SVN_ERR_MALFUNCTION();
703 /* merely silence a compiler warning (this will never be executed) */
707 /* This implements `svn_opt_subcommand_t'. */
709 subcommand_create(apr_getopt_t *os, void *baton, apr_pool_t *pool)
711 struct svnadmin_opt_state *opt_state = baton;
713 apr_hash_t *fs_config = apr_hash_make(pool);
715 /* Expect no more arguments. */
716 SVN_ERR(parse_args(NULL, os, 0, 0, pool));
718 svn_hash_sets(fs_config, SVN_FS_CONFIG_BDB_TXN_NOSYNC,
719 (opt_state->bdb_txn_nosync ? "1" :"0"));
721 svn_hash_sets(fs_config, SVN_FS_CONFIG_BDB_LOG_AUTOREMOVE,
722 (opt_state->bdb_log_keep ? "0" :"1"));
724 if (opt_state->fs_type)
726 /* With 1.8 we are announcing that BDB is deprecated. No support
727 * has been removed and it will continue to work until some future
728 * date. The purpose here is to discourage people from creating
729 * new BDB repositories which they will need to dump/load into
730 * FSFS or some new FS type in the future. */
731 if (0 == strcmp(opt_state->fs_type, SVN_FS_TYPE_BDB))
733 SVN_ERR(svn_cmdline_fprintf(
736 " The \"%s\" repository back-end is deprecated,"
737 " consider using \"%s\" instead.\n"),
738 "svnadmin: ", SVN_FS_TYPE_BDB, SVN_FS_TYPE_FSFS));
741 svn_hash_sets(fs_config, SVN_FS_CONFIG_FS_TYPE, opt_state->fs_type);
744 if (opt_state->compatible_version)
746 if (! svn_version__at_least(opt_state->compatible_version, 1, 4, 0))
747 svn_hash_sets(fs_config, SVN_FS_CONFIG_PRE_1_4_COMPATIBLE, "1");
748 if (! svn_version__at_least(opt_state->compatible_version, 1, 5, 0))
749 svn_hash_sets(fs_config, SVN_FS_CONFIG_PRE_1_5_COMPATIBLE, "1");
750 if (! svn_version__at_least(opt_state->compatible_version, 1, 6, 0))
751 svn_hash_sets(fs_config, SVN_FS_CONFIG_PRE_1_6_COMPATIBLE, "1");
752 if (! svn_version__at_least(opt_state->compatible_version, 1, 8, 0))
753 svn_hash_sets(fs_config, SVN_FS_CONFIG_PRE_1_8_COMPATIBLE, "1");
754 /* In 1.9, we figured out that we didn't have to keep extending this
755 madness indefinitely. */
756 svn_hash_sets(fs_config, SVN_FS_CONFIG_COMPATIBLE_VERSION,
757 apr_psprintf(pool, "%d.%d.%d%s%s",
758 opt_state->compatible_version->major,
759 opt_state->compatible_version->minor,
760 opt_state->compatible_version->patch,
761 opt_state->compatible_version->tag
763 opt_state->compatible_version->tag
764 ? opt_state->compatible_version->tag : ""));
767 if (opt_state->compatible_version)
769 if (! svn_version__at_least(opt_state->compatible_version, 1, 1, 0)
770 /* ### TODO: this NULL check hard-codes knowledge of the library's
771 default fs-type value */
772 && (opt_state->fs_type == NULL
773 || !strcmp(opt_state->fs_type, SVN_FS_TYPE_FSFS)))
775 return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
776 _("Repositories compatible with 1.0.x must "
777 "use --fs-type=bdb"));
780 if (! svn_version__at_least(opt_state->compatible_version, 1, 9, 0)
781 && opt_state->fs_type && !strcmp(opt_state->fs_type, SVN_FS_TYPE_FSX))
783 return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
784 _("Repositories compatible with 1.8.x or "
785 "earlier cannot use --fs-type=%s"),
790 SVN_ERR(svn_repos_create(&repos, opt_state->repository_path,
791 NULL, NULL, NULL, fs_config, pool));
792 svn_fs_set_warning_func(svn_repos_fs(repos), warning_func, NULL);
797 /* This implements `svn_opt_subcommand_t'. */
799 subcommand_deltify(apr_getopt_t *os, void *baton, apr_pool_t *pool)
801 struct svnadmin_opt_state *opt_state = baton;
804 svn_revnum_t start = SVN_INVALID_REVNUM, end = SVN_INVALID_REVNUM;
805 svn_revnum_t youngest, revision;
806 apr_pool_t *subpool = svn_pool_create(pool);
808 /* Expect no more arguments. */
809 SVN_ERR(parse_args(NULL, os, 0, 0, pool));
811 SVN_ERR(open_repos(&repos, opt_state->repository_path, pool));
812 fs = svn_repos_fs(repos);
813 SVN_ERR(svn_fs_youngest_rev(&youngest, fs, pool));
815 /* Find the revision numbers at which to start and end. */
816 SVN_ERR(get_revnum(&start, &opt_state->start_revision,
817 youngest, repos, pool));
818 SVN_ERR(get_revnum(&end, &opt_state->end_revision,
819 youngest, repos, pool));
821 /* Fill in implied revisions if necessary. */
822 if (start == SVN_INVALID_REVNUM)
824 if (end == SVN_INVALID_REVNUM)
828 return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
829 _("First revision cannot be higher than second"));
831 /* Loop over the requested revision range, performing the
832 predecessor deltification on paths changed in each. */
833 for (revision = start; revision <= end; revision++)
835 svn_pool_clear(subpool);
836 SVN_ERR(check_cancel(NULL));
837 if (! opt_state->quiet)
838 SVN_ERR(svn_cmdline_printf(subpool, _("Deltifying revision %ld..."),
840 SVN_ERR(svn_fs_deltify_revision(fs, revision, subpool));
841 if (! opt_state->quiet)
842 SVN_ERR(svn_cmdline_printf(subpool, _("done.\n")));
844 svn_pool_destroy(subpool);
849 /* Structure for errors encountered during 'svnadmin verify --keep-going'. */
850 struct verification_error
856 /* Pool cleanup function to clear an svn_error_t *. */
858 err_cleanup(void *data)
860 svn_error_t *err = data;
862 svn_error_clear(err);
867 struct repos_verify_callback_baton
869 /* Should we continue after receiving a first verification error? */
870 svn_boolean_t keep_going;
872 /* List of errors encountered during 'svnadmin verify --keep-going'. */
873 apr_array_header_t *error_summary;
875 /* Pool for data collected during callback invocations. */
876 apr_pool_t *result_pool;
879 /* Implementation of svn_repos_verify_callback_t to handle errors coming
880 from svn_repos_verify_fs3(). */
882 repos_verify_callback(void *baton,
883 svn_revnum_t revision,
884 svn_error_t *verify_err,
885 apr_pool_t *scratch_pool)
887 struct repos_verify_callback_baton *b = baton;
889 if (revision == SVN_INVALID_REVNUM)
891 SVN_ERR(svn_cmdline_fputs(_("* Error verifying repository metadata.\n"),
892 stderr, scratch_pool));
896 SVN_ERR(svn_cmdline_fprintf(stderr, scratch_pool,
897 _("* Error verifying revision %ld.\n"),
903 struct verification_error *verr;
905 svn_handle_error2(verify_err, stderr, FALSE, "svnadmin: ");
907 /* Remember the error in B->ERROR_SUMMARY. */
908 verr = apr_palloc(b->result_pool, sizeof(*verr));
909 verr->rev = revision;
910 verr->err = svn_error_dup(verify_err);
911 apr_pool_cleanup_register(b->result_pool, verr->err, err_cleanup,
912 apr_pool_cleanup_null);
913 APR_ARRAY_PUSH(b->error_summary, struct verification_error *) = verr;
918 return svn_error_trace(svn_error_dup(verify_err));
921 /* Implementation of svn_repos_notify_func_t to wrap the output to a
922 response stream for svn_repos_dump_fs2(), svn_repos_verify_fs(),
923 svn_repos_hotcopy3() and others. */
925 repos_notify_handler(void *baton,
926 const svn_repos_notify_t *notify,
927 apr_pool_t *scratch_pool)
929 svn_stream_t *feedback_stream = baton;
931 switch (notify->action)
933 case svn_repos_notify_warning:
934 svn_error_clear(svn_stream_printf(feedback_stream, scratch_pool,
935 "WARNING 0x%04x: %s\n", notify->warning,
936 notify->warning_str));
939 case svn_repos_notify_dump_rev_end:
940 svn_error_clear(svn_stream_printf(feedback_stream, scratch_pool,
941 _("* Dumped revision %ld.\n"),
945 case svn_repos_notify_verify_rev_end:
946 svn_error_clear(svn_stream_printf(feedback_stream, scratch_pool,
947 _("* Verified revision %ld.\n"),
951 case svn_repos_notify_verify_rev_structure:
952 if (notify->revision == SVN_INVALID_REVNUM)
953 svn_error_clear(svn_stream_puts(feedback_stream,
954 _("* Verifying repository metadata ...\n")));
956 svn_error_clear(svn_stream_printf(feedback_stream, scratch_pool,
957 _("* Verifying metadata at revision %ld ...\n"),
961 case svn_repos_notify_pack_shard_start:
963 const char *shardstr = apr_psprintf(scratch_pool,
966 svn_error_clear(svn_stream_printf(feedback_stream, scratch_pool,
967 _("Packing revisions in shard %s..."),
972 case svn_repos_notify_pack_shard_end:
973 svn_error_clear(svn_stream_puts(feedback_stream, _("done.\n")));
976 case svn_repos_notify_pack_shard_start_revprop:
978 const char *shardstr = apr_psprintf(scratch_pool,
981 svn_error_clear(svn_stream_printf(feedback_stream, scratch_pool,
982 _("Packing revprops in shard %s..."),
987 case svn_repos_notify_pack_shard_end_revprop:
988 svn_error_clear(svn_stream_puts(feedback_stream, _("done.\n")));
991 case svn_repos_notify_load_txn_committed:
992 if (notify->old_revision == SVN_INVALID_REVNUM)
994 svn_error_clear(svn_stream_printf(feedback_stream, scratch_pool,
995 _("\n------- Committed revision %ld >>>\n\n"),
996 notify->new_revision));
1000 svn_error_clear(svn_stream_printf(feedback_stream, scratch_pool,
1001 _("\n------- Committed new rev %ld"
1002 " (loaded from original rev %ld"
1003 ") >>>\n\n"), notify->new_revision,
1004 notify->old_revision));
1008 case svn_repos_notify_load_node_start:
1010 switch (notify->node_action)
1012 case svn_node_action_change:
1013 svn_error_clear(svn_stream_printf(feedback_stream, scratch_pool,
1014 _(" * editing path : %s ..."),
1018 case svn_node_action_delete:
1019 svn_error_clear(svn_stream_printf(feedback_stream, scratch_pool,
1020 _(" * deleting path : %s ..."),
1024 case svn_node_action_add:
1025 svn_error_clear(svn_stream_printf(feedback_stream, scratch_pool,
1026 _(" * adding path : %s ..."),
1030 case svn_node_action_replace:
1031 svn_error_clear(svn_stream_printf(feedback_stream, scratch_pool,
1032 _(" * replacing path : %s ..."),
1040 case svn_repos_notify_load_node_done:
1041 svn_error_clear(svn_stream_puts(feedback_stream, _(" done.\n")));
1044 case svn_repos_notify_load_copied_node:
1045 svn_error_clear(svn_stream_puts(feedback_stream, "COPIED..."));
1048 case svn_repos_notify_load_txn_start:
1049 svn_error_clear(svn_stream_printf(feedback_stream, scratch_pool,
1050 _("<<< Started new transaction, based on "
1051 "original revision %ld\n"),
1052 notify->old_revision));
1055 case svn_repos_notify_load_skipped_rev:
1056 svn_error_clear(svn_stream_printf(feedback_stream, scratch_pool,
1057 _("<<< Skipped original revision %ld\n"),
1058 notify->old_revision));
1061 case svn_repos_notify_load_normalized_mergeinfo:
1062 svn_error_clear(svn_stream_printf(feedback_stream, scratch_pool,
1063 _(" removing '\\r' from %s ..."),
1064 SVN_PROP_MERGEINFO));
1067 case svn_repos_notify_mutex_acquired:
1068 /* Enable cancellation signal handlers. */
1069 setup_cancellation_signals(signal_handler);
1072 case svn_repos_notify_recover_start:
1073 svn_error_clear(svn_stream_puts(feedback_stream,
1074 _("Repository lock acquired.\n"
1075 "Please wait; recovering the"
1076 " repository may take some time...\n")));
1079 case svn_repos_notify_upgrade_start:
1080 svn_error_clear(svn_stream_puts(feedback_stream,
1081 _("Repository lock acquired.\n"
1082 "Please wait; upgrading the"
1083 " repository may take some time...\n")));
1086 case svn_repos_notify_pack_revprops:
1088 const char *shardstr = apr_psprintf(scratch_pool,
1089 "%" APR_INT64_T_FMT,
1091 svn_error_clear(svn_stream_printf(feedback_stream, scratch_pool,
1092 _("Packed revision properties in shard %s\n"),
1097 case svn_repos_notify_cleanup_revprops:
1099 const char *shardstr = apr_psprintf(scratch_pool,
1100 "%" APR_INT64_T_FMT,
1102 svn_error_clear(svn_stream_printf(feedback_stream, scratch_pool,
1103 _("Removed non-packed revision properties"
1109 case svn_repos_notify_format_bumped:
1110 svn_error_clear(svn_stream_printf(feedback_stream, scratch_pool,
1111 _("Bumped repository format to %ld\n"),
1115 case svn_repos_notify_hotcopy_rev_range:
1116 if (notify->start_revision == notify->end_revision)
1118 svn_error_clear(svn_stream_printf(feedback_stream, scratch_pool,
1119 _("* Copied revision %ld.\n"),
1120 notify->start_revision));
1124 svn_error_clear(svn_stream_printf(feedback_stream, scratch_pool,
1125 _("* Copied revisions from %ld to %ld.\n"),
1126 notify->start_revision, notify->end_revision));
1135 /* Baton for recode_write(). */
1136 struct recode_write_baton
1142 /* This implements the 'svn_write_fn_t' interface.
1144 Write DATA to ((struct recode_write_baton *) BATON)->out, in the
1145 console encoding, using svn_cmdline_fprintf(). DATA is a
1146 UTF8-encoded C string, therefore ignore LEN.
1148 ### This recoding mechanism might want to be abstracted into
1149 ### svn_io.h or svn_cmdline.h, if it proves useful elsewhere. */
1150 static svn_error_t *recode_write(void *baton,
1154 struct recode_write_baton *rwb = baton;
1155 svn_pool_clear(rwb->pool);
1156 return svn_cmdline_fputs(data, rwb->out, rwb->pool);
1159 /* Create a stream, to write to STD_STREAM, that uses recode_write()
1160 to perform UTF-8 to console encoding translation. */
1161 static svn_stream_t *
1162 recode_stream_create(FILE *std_stream, apr_pool_t *pool)
1164 struct recode_write_baton *std_stream_rwb =
1165 apr_palloc(pool, sizeof(struct recode_write_baton));
1167 svn_stream_t *rw_stream = svn_stream_create(std_stream_rwb, pool);
1168 std_stream_rwb->pool = svn_pool_create(pool);
1169 std_stream_rwb->out = std_stream;
1170 svn_stream_set_write(rw_stream, recode_write);
1175 /* This implements `svn_opt_subcommand_t'. */
1176 static svn_error_t *
1177 subcommand_dump(apr_getopt_t *os, void *baton, apr_pool_t *pool)
1179 struct svnadmin_opt_state *opt_state = baton;
1182 svn_stream_t *stdout_stream;
1183 svn_revnum_t lower = SVN_INVALID_REVNUM, upper = SVN_INVALID_REVNUM;
1184 svn_revnum_t youngest;
1185 svn_stream_t *feedback_stream = NULL;
1187 /* Expect no more arguments. */
1188 SVN_ERR(parse_args(NULL, os, 0, 0, pool));
1190 SVN_ERR(open_repos(&repos, opt_state->repository_path, pool));
1191 fs = svn_repos_fs(repos);
1192 SVN_ERR(svn_fs_youngest_rev(&youngest, fs, pool));
1194 /* Find the revision numbers at which to start and end. */
1195 SVN_ERR(get_revnum(&lower, &opt_state->start_revision,
1196 youngest, repos, pool));
1197 SVN_ERR(get_revnum(&upper, &opt_state->end_revision,
1198 youngest, repos, pool));
1200 /* Fill in implied revisions if necessary. */
1201 if (lower == SVN_INVALID_REVNUM)
1206 else if (upper == SVN_INVALID_REVNUM)
1212 return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
1213 _("First revision cannot be higher than second"));
1215 SVN_ERR(svn_stream_for_stdout(&stdout_stream, pool));
1217 /* Progress feedback goes to STDERR, unless they asked to suppress it. */
1218 if (! opt_state->quiet)
1219 feedback_stream = recode_stream_create(stderr, pool);
1221 SVN_ERR(svn_repos_dump_fs3(repos, stdout_stream, lower, upper,
1222 opt_state->incremental, opt_state->use_deltas,
1223 !opt_state->quiet ? repos_notify_handler : NULL,
1224 feedback_stream, check_cancel, NULL, pool));
1226 return SVN_NO_ERROR;
1229 struct freeze_baton_t {
1230 const char *command;
1235 /* Implements svn_repos_freeze_func_t */
1236 static svn_error_t *
1237 freeze_body(void *baton,
1240 struct freeze_baton_t *b = baton;
1241 apr_status_t apr_err;
1242 apr_file_t *infile, *outfile, *errfile;
1244 apr_err = apr_file_open_stdin(&infile, pool);
1246 return svn_error_wrap_apr(apr_err, "Can't open stdin");
1247 apr_err = apr_file_open_stdout(&outfile, pool);
1249 return svn_error_wrap_apr(apr_err, "Can't open stdout");
1250 apr_err = apr_file_open_stderr(&errfile, pool);
1252 return svn_error_wrap_apr(apr_err, "Can't open stderr");
1254 SVN_ERR(svn_io_run_cmd(NULL, b->command, b->args, &b->status,
1256 infile, outfile, errfile, pool));
1258 return SVN_NO_ERROR;
1261 static svn_error_t *
1262 subcommand_freeze(apr_getopt_t *os, void *baton, apr_pool_t *pool)
1264 struct svnadmin_opt_state *opt_state = baton;
1265 apr_array_header_t *paths;
1266 apr_array_header_t *args;
1268 struct freeze_baton_t b;
1270 SVN_ERR(svn_opt_parse_all_args(&args, os, pool));
1273 return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, 0,
1274 _("No program provided"));
1276 if (!opt_state->filedata)
1278 /* One repository on the command line. */
1279 paths = apr_array_make(pool, 1, sizeof(const char *));
1280 APR_ARRAY_PUSH(paths, const char *) = opt_state->repository_path;
1285 /* All repositories in filedata. */
1286 SVN_ERR(svn_utf_cstring_to_utf8(&utf8, opt_state->filedata->data, pool));
1287 paths = svn_cstring_split(utf8, "\r\n", FALSE, pool);
1290 b.command = APR_ARRAY_IDX(args, 0, const char *);
1291 b.args = apr_palloc(pool, sizeof(char *) * (args->nelts + 1));
1292 for (i = 0; i < args->nelts; ++i)
1293 b.args[i] = APR_ARRAY_IDX(args, i, const char *);
1294 b.args[args->nelts] = NULL;
1296 SVN_ERR(svn_repos_freeze(paths, freeze_body, &b, pool));
1298 /* Make any non-zero status visible to the user. */
1302 return SVN_NO_ERROR;
1306 /* This implements `svn_opt_subcommand_t'. */
1307 static svn_error_t *
1308 subcommand_help(apr_getopt_t *os, void *baton, apr_pool_t *pool)
1310 struct svnadmin_opt_state *opt_state = baton;
1311 const char *header =
1312 _("general usage: svnadmin SUBCOMMAND REPOS_PATH [ARGS & OPTIONS ...]\n"
1313 "Subversion repository administration tool.\n"
1314 "Type 'svnadmin help <subcommand>' for help on a specific subcommand.\n"
1315 "Type 'svnadmin --version' to see the program version and FS modules.\n"
1317 "Available subcommands:\n");
1319 const char *fs_desc_start
1320 = _("The following repository back-end (FS) modules are available:\n\n");
1322 svn_stringbuf_t *version_footer;
1324 version_footer = svn_stringbuf_create(fs_desc_start, pool);
1325 SVN_ERR(svn_fs_print_modules(version_footer, pool));
1327 SVN_ERR(svn_opt_print_help4(os, "svnadmin",
1328 opt_state ? opt_state->version : FALSE,
1329 opt_state ? opt_state->quiet : FALSE,
1330 /*###opt_state ? opt_state->verbose :*/ FALSE,
1331 version_footer->data,
1332 header, cmd_table, options_table, NULL, NULL,
1335 return SVN_NO_ERROR;
1339 /* Set *REVNUM to the revision number of a numeric REV, or to
1340 SVN_INVALID_REVNUM if REV is unspecified. */
1341 static svn_error_t *
1342 optrev_to_revnum(svn_revnum_t *revnum, const svn_opt_revision_t *opt_rev)
1344 if (opt_rev->kind == svn_opt_revision_number)
1346 *revnum = opt_rev->value.number;
1347 if (! SVN_IS_VALID_REVNUM(*revnum))
1348 return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
1349 _("Invalid revision number (%ld) specified"),
1352 else if (opt_rev->kind == svn_opt_revision_unspecified)
1354 *revnum = SVN_INVALID_REVNUM;
1358 return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
1359 _("Non-numeric revision specified"));
1361 return SVN_NO_ERROR;
1365 /* This implements `svn_opt_subcommand_t'. */
1366 static svn_error_t *
1367 subcommand_load(apr_getopt_t *os, void *baton, apr_pool_t *pool)
1370 struct svnadmin_opt_state *opt_state = baton;
1372 svn_revnum_t lower = SVN_INVALID_REVNUM, upper = SVN_INVALID_REVNUM;
1373 svn_stream_t *stdin_stream;
1374 svn_stream_t *feedback_stream = NULL;
1376 /* Expect no more arguments. */
1377 SVN_ERR(parse_args(NULL, os, 0, 0, pool));
1379 /* Find the revision numbers at which to start and end. We only
1380 support a limited set of revision kinds: number and unspecified. */
1381 SVN_ERR(optrev_to_revnum(&lower, &opt_state->start_revision));
1382 SVN_ERR(optrev_to_revnum(&upper, &opt_state->end_revision));
1384 /* Fill in implied revisions if necessary. */
1385 if ((upper == SVN_INVALID_REVNUM) && (lower != SVN_INVALID_REVNUM))
1389 else if ((upper != SVN_INVALID_REVNUM) && (lower == SVN_INVALID_REVNUM))
1394 /* Ensure correct range ordering. */
1397 return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
1398 _("First revision cannot be higher than second"));
1401 SVN_ERR(open_repos(&repos, opt_state->repository_path, pool));
1403 /* Read the stream from STDIN. Users can redirect a file. */
1404 SVN_ERR(svn_stream_for_stdin(&stdin_stream, pool));
1406 /* Progress feedback goes to STDOUT, unless they asked to suppress it. */
1407 if (! opt_state->quiet)
1408 feedback_stream = recode_stream_create(stdout, pool);
1410 err = svn_repos_load_fs5(repos, stdin_stream, lower, upper,
1411 opt_state->uuid_action, opt_state->parent_dir,
1412 opt_state->use_pre_commit_hook,
1413 opt_state->use_post_commit_hook,
1414 !opt_state->bypass_prop_validation,
1415 opt_state->ignore_dates,
1416 opt_state->quiet ? NULL : repos_notify_handler,
1417 feedback_stream, check_cancel, NULL, pool);
1418 if (err && err->apr_err == SVN_ERR_BAD_PROPERTY_VALUE)
1419 return svn_error_quick_wrap(err,
1420 _("Invalid property value found in "
1421 "dumpstream; consider repairing the source "
1422 "or using --bypass-prop-validation while "
1428 /* This implements `svn_opt_subcommand_t'. */
1429 static svn_error_t *
1430 subcommand_lstxns(apr_getopt_t *os, void *baton, apr_pool_t *pool)
1432 struct svnadmin_opt_state *opt_state = baton;
1435 apr_array_header_t *txns;
1438 /* Expect no more arguments. */
1439 SVN_ERR(parse_args(NULL, os, 0, 0, pool));
1441 SVN_ERR(open_repos(&repos, opt_state->repository_path, pool));
1442 fs = svn_repos_fs(repos);
1443 SVN_ERR(svn_fs_list_transactions(&txns, fs, pool));
1445 /* Loop, printing revisions. */
1446 for (i = 0; i < txns->nelts; i++)
1448 SVN_ERR(svn_cmdline_printf(pool, "%s\n",
1449 APR_ARRAY_IDX(txns, i, const char *)));
1452 return SVN_NO_ERROR;
1456 /* This implements `svn_opt_subcommand_t'. */
1457 static svn_error_t *
1458 subcommand_recover(apr_getopt_t *os, void *baton, apr_pool_t *pool)
1460 svn_revnum_t youngest_rev;
1463 struct svnadmin_opt_state *opt_state = baton;
1464 svn_stream_t *feedback_stream = NULL;
1466 /* Expect no more arguments. */
1467 SVN_ERR(parse_args(NULL, os, 0, 0, pool));
1469 SVN_ERR(svn_stream_for_stdout(&feedback_stream, pool));
1471 /* Restore default signal handlers until after we have acquired the
1472 * exclusive lock so that the user interrupt before we actually
1473 * touch the repository. */
1474 setup_cancellation_signals(SIG_DFL);
1476 err = svn_repos_recover4(opt_state->repository_path, TRUE,
1477 repos_notify_handler, feedback_stream,
1478 check_cancel, NULL, pool);
1481 if (! APR_STATUS_IS_EAGAIN(err->apr_err))
1483 svn_error_clear(err);
1484 if (! opt_state->wait)
1485 return svn_error_create(SVN_ERR_REPOS_LOCKED, NULL,
1486 _("Failed to get exclusive repository "
1487 "access; perhaps another process\n"
1488 "such as httpd, svnserve or svn "
1490 SVN_ERR(svn_cmdline_printf(pool,
1491 _("Waiting on repository lock; perhaps"
1492 " another process has it open?\n")));
1493 SVN_ERR(svn_cmdline_fflush(stdout));
1494 SVN_ERR(svn_repos_recover4(opt_state->repository_path, FALSE,
1495 repos_notify_handler, feedback_stream,
1496 check_cancel, NULL, pool));
1499 SVN_ERR(svn_cmdline_printf(pool, _("\nRecovery completed.\n")));
1501 /* Since db transactions may have been replayed, it's nice to tell
1502 people what the latest revision is. It also proves that the
1503 recovery actually worked. */
1504 SVN_ERR(open_repos(&repos, opt_state->repository_path, pool));
1505 SVN_ERR(svn_fs_youngest_rev(&youngest_rev, svn_repos_fs(repos), pool));
1506 SVN_ERR(svn_cmdline_printf(pool, _("The latest repos revision is %ld.\n"),
1509 return SVN_NO_ERROR;
1513 /* This implements `svn_opt_subcommand_t'. */
1514 static svn_error_t *
1515 list_dblogs(apr_getopt_t *os, void *baton, svn_boolean_t only_unused,
1518 struct svnadmin_opt_state *opt_state = baton;
1519 apr_array_header_t *logfiles;
1522 /* Expect no more arguments. */
1523 SVN_ERR(parse_args(NULL, os, 0, 0, pool));
1525 SVN_ERR(svn_repos_db_logfiles(&logfiles,
1526 opt_state->repository_path,
1530 /* Loop, printing log files. We append the log paths to the
1531 repository path, making sure to return everything to the native
1532 style before printing. */
1533 for (i = 0; i < logfiles->nelts; i++)
1535 const char *log_utf8;
1536 log_utf8 = svn_dirent_join(opt_state->repository_path,
1537 APR_ARRAY_IDX(logfiles, i, const char *),
1539 log_utf8 = svn_dirent_local_style(log_utf8, pool);
1540 SVN_ERR(svn_cmdline_printf(pool, "%s\n", log_utf8));
1543 return SVN_NO_ERROR;
1547 /* This implements `svn_opt_subcommand_t'. */
1548 static svn_error_t *
1549 subcommand_list_dblogs(apr_getopt_t *os, void *baton, apr_pool_t *pool)
1551 SVN_ERR(list_dblogs(os, baton, FALSE, pool));
1552 return SVN_NO_ERROR;
1556 /* This implements `svn_opt_subcommand_t'. */
1557 static svn_error_t *
1558 subcommand_list_unused_dblogs(apr_getopt_t *os, void *baton, apr_pool_t *pool)
1560 /* Expect no more arguments. */
1561 SVN_ERR(parse_args(NULL, os, 0, 0, pool));
1563 SVN_ERR(list_dblogs(os, baton, TRUE, pool));
1564 return SVN_NO_ERROR;
1568 /* This implements `svn_opt_subcommand_t'. */
1569 static svn_error_t *
1570 subcommand_rmtxns(apr_getopt_t *os, void *baton, apr_pool_t *pool)
1572 struct svnadmin_opt_state *opt_state = baton;
1576 apr_array_header_t *args;
1578 apr_pool_t *subpool = svn_pool_create(pool);
1580 SVN_ERR(svn_opt_parse_all_args(&args, os, pool));
1582 SVN_ERR(open_repos(&repos, opt_state->repository_path, pool));
1583 fs = svn_repos_fs(repos);
1585 /* All the rest of the arguments are transaction names. */
1586 for (i = 0; i < args->nelts; i++)
1588 const char *txn_name = APR_ARRAY_IDX(args, i, const char *);
1589 const char *txn_name_utf8;
1592 svn_pool_clear(subpool);
1594 SVN_ERR(svn_utf_cstring_to_utf8(&txn_name_utf8, txn_name, subpool));
1596 /* Try to open the txn. If that succeeds, try to abort it. */
1597 err = svn_fs_open_txn(&txn, fs, txn_name_utf8, subpool);
1599 err = svn_fs_abort_txn(txn, subpool);
1601 /* If either the open or the abort of the txn fails because that
1602 transaction is dead, just try to purge the thing. Else,
1603 there was either an error worth reporting, or not error at
1605 if (err && (err->apr_err == SVN_ERR_FS_TRANSACTION_DEAD))
1607 svn_error_clear(err);
1608 err = svn_fs_purge_txn(fs, txn_name_utf8, subpool);
1611 /* If we had a real from the txn open, abort, or purge, we clear
1612 that error and just report to the user that we had an issue
1613 with this particular txn. */
1616 svn_handle_error2(err, stderr, FALSE /* non-fatal */, "svnadmin: ");
1617 svn_error_clear(err);
1619 else if (! opt_state->quiet)
1621 SVN_ERR(svn_cmdline_printf(subpool, _("Transaction '%s' removed.\n"),
1626 svn_pool_destroy(subpool);
1628 return SVN_NO_ERROR;
1632 /* A helper for the 'setrevprop' and 'setlog' commands. Expects
1633 OPT_STATE->txn_id, OPT_STATE->use_pre_revprop_change_hook and
1634 OPT_STATE->use_post_revprop_change_hook to be set appropriately.
1635 If FILENAME is NULL, delete property PROP_NAME. */
1636 static svn_error_t *
1637 set_revprop(const char *prop_name, const char *filename,
1638 struct svnadmin_opt_state *opt_state, apr_pool_t *pool)
1641 svn_string_t *prop_value;
1645 svn_stringbuf_t *file_contents;
1647 SVN_ERR(svn_stringbuf_from_file2(&file_contents, filename, pool));
1649 prop_value = svn_string_create_empty(pool);
1650 prop_value->data = file_contents->data;
1651 prop_value->len = file_contents->len;
1653 SVN_ERR(svn_subst_translate_string2(&prop_value, NULL, NULL, prop_value,
1654 NULL, FALSE, pool, pool));
1661 /* Open the filesystem */
1662 SVN_ERR(open_repos(&repos, opt_state->repository_path, pool));
1664 if (opt_state->txn_id)
1666 svn_fs_t *fs = svn_repos_fs(repos);
1669 SVN_ERR(svn_fs_open_txn(&txn, fs, opt_state->txn_id, pool));
1670 SVN_ERR(svn_fs_change_txn_prop(txn, prop_name, prop_value, pool));
1673 SVN_ERR(svn_repos_fs_change_rev_prop4(
1674 repos, opt_state->start_revision.value.number,
1675 NULL, prop_name, NULL, prop_value,
1676 opt_state->use_pre_revprop_change_hook,
1677 opt_state->use_post_revprop_change_hook,
1680 return SVN_NO_ERROR;
1684 /* This implements `svn_opt_subcommand_t'. */
1685 static svn_error_t *
1686 subcommand_setrevprop(apr_getopt_t *os, void *baton, apr_pool_t *pool)
1688 struct svnadmin_opt_state *opt_state = baton;
1689 apr_array_header_t *args;
1690 const char *prop_name, *filename;
1692 /* Expect two more arguments: NAME FILE */
1693 SVN_ERR(parse_args(&args, os, 2, 2, pool));
1694 prop_name = APR_ARRAY_IDX(args, 0, const char *);
1695 filename = APR_ARRAY_IDX(args, 1, const char *);
1696 SVN_ERR(target_arg_to_dirent(&filename, filename, pool));
1698 if (opt_state->txn_id)
1700 if (opt_state->start_revision.kind != svn_opt_revision_unspecified
1701 || opt_state->end_revision.kind != svn_opt_revision_unspecified)
1702 return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
1703 _("--revision (-r) and --transaction (-t) "
1704 "are mutually exclusive"));
1706 if (opt_state->use_pre_revprop_change_hook
1707 || opt_state->use_post_revprop_change_hook)
1708 return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
1709 _("Calling hooks is incompatible with "
1710 "--transaction (-t)"));
1712 else if (opt_state->start_revision.kind != svn_opt_revision_number)
1713 return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
1714 _("Missing revision"));
1715 else if (opt_state->end_revision.kind != svn_opt_revision_unspecified)
1716 return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
1717 _("Only one revision allowed"));
1719 return set_revprop(prop_name, filename, opt_state, pool);
1723 /* This implements `svn_opt_subcommand_t'. */
1724 static svn_error_t *
1725 subcommand_setuuid(apr_getopt_t *os, void *baton, apr_pool_t *pool)
1727 struct svnadmin_opt_state *opt_state = baton;
1728 apr_array_header_t *args;
1731 const char *uuid = NULL;
1733 /* Expect zero or one more arguments: [UUID] */
1734 SVN_ERR(parse_args(&args, os, 0, 1, pool));
1735 if (args->nelts == 1)
1736 uuid = APR_ARRAY_IDX(args, 0, const char *);
1738 SVN_ERR(open_repos(&repos, opt_state->repository_path, pool));
1739 fs = svn_repos_fs(repos);
1740 return svn_fs_set_uuid(fs, uuid, pool);
1744 /* This implements `svn_opt_subcommand_t'. */
1745 static svn_error_t *
1746 subcommand_setlog(apr_getopt_t *os, void *baton, apr_pool_t *pool)
1748 struct svnadmin_opt_state *opt_state = baton;
1749 apr_array_header_t *args;
1750 const char *filename;
1752 /* Expect one more argument: FILE */
1753 SVN_ERR(parse_args(&args, os, 1, 1, pool));
1754 filename = APR_ARRAY_IDX(args, 0, const char *);
1755 SVN_ERR(target_arg_to_dirent(&filename, filename, pool));
1757 if (opt_state->start_revision.kind != svn_opt_revision_number)
1758 return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
1759 _("Missing revision"));
1760 else if (opt_state->end_revision.kind != svn_opt_revision_unspecified)
1761 return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
1762 _("Only one revision allowed"));
1764 /* set_revprop() responds only to pre-/post-revprop-change opts. */
1765 if (!opt_state->bypass_hooks)
1767 opt_state->use_pre_revprop_change_hook = TRUE;
1768 opt_state->use_post_revprop_change_hook = TRUE;
1771 return set_revprop(SVN_PROP_REVISION_LOG, filename, opt_state, pool);
1775 /* This implements 'svn_opt_subcommand_t'. */
1776 static svn_error_t *
1777 subcommand_pack(apr_getopt_t *os, void *baton, apr_pool_t *pool)
1779 struct svnadmin_opt_state *opt_state = baton;
1781 svn_stream_t *feedback_stream = NULL;
1783 /* Expect no more arguments. */
1784 SVN_ERR(parse_args(NULL, os, 0, 0, pool));
1786 SVN_ERR(open_repos(&repos, opt_state->repository_path, pool));
1788 /* Progress feedback goes to STDOUT, unless they asked to suppress it. */
1789 if (! opt_state->quiet)
1790 feedback_stream = recode_stream_create(stdout, pool);
1792 return svn_error_trace(
1793 svn_repos_fs_pack2(repos, !opt_state->quiet ? repos_notify_handler : NULL,
1794 feedback_stream, check_cancel, NULL, pool));
1798 /* This implements `svn_opt_subcommand_t'. */
1799 static svn_error_t *
1800 subcommand_verify(apr_getopt_t *os, void *baton, apr_pool_t *pool)
1802 struct svnadmin_opt_state *opt_state = baton;
1805 svn_revnum_t youngest, lower, upper;
1806 svn_stream_t *feedback_stream = NULL;
1807 struct repos_verify_callback_baton verify_baton = { 0 };
1809 /* Expect no more arguments. */
1810 SVN_ERR(parse_args(NULL, os, 0, 0, pool));
1812 if (opt_state->txn_id
1813 && (opt_state->start_revision.kind != svn_opt_revision_unspecified
1814 || opt_state->end_revision.kind != svn_opt_revision_unspecified))
1816 return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
1817 _("--revision (-r) and --transaction (-t) "
1818 "are mutually exclusive"));
1821 SVN_ERR(open_repos(&repos, opt_state->repository_path, pool));
1822 fs = svn_repos_fs(repos);
1823 SVN_ERR(svn_fs_youngest_rev(&youngest, fs, pool));
1826 if (opt_state->txn_id)
1829 svn_fs_root_t *root;
1831 SVN_ERR(svn_fs_open_txn(&txn, fs, opt_state->txn_id, pool));
1832 SVN_ERR(svn_fs_txn_root(&root, txn, pool));
1833 SVN_ERR(svn_fs_verify_root(root, pool));
1834 return SVN_NO_ERROR;
1840 /* Find the revision numbers at which to start and end. */
1841 SVN_ERR(get_revnum(&lower, &opt_state->start_revision,
1842 youngest, repos, pool));
1843 SVN_ERR(get_revnum(&upper, &opt_state->end_revision,
1844 youngest, repos, pool));
1846 if (upper == SVN_INVALID_REVNUM)
1851 if (!opt_state->quiet)
1852 feedback_stream = recode_stream_create(stdout, pool);
1854 verify_baton.keep_going = opt_state->keep_going;
1855 verify_baton.error_summary =
1856 apr_array_make(pool, 0, sizeof(struct verification_error *));
1857 verify_baton.result_pool = pool;
1859 SVN_ERR(svn_repos_verify_fs3(repos, lower, upper,
1860 opt_state->check_normalization,
1861 opt_state->metadata_only,
1863 ? repos_notify_handler : NULL,
1865 repos_verify_callback, &verify_baton,
1866 check_cancel, NULL, pool));
1868 /* Show the --keep-going error summary. */
1869 if (!opt_state->quiet
1870 && opt_state->keep_going
1871 && verify_baton.error_summary->nelts > 0)
1874 svn_revnum_t end_revnum;
1875 apr_pool_t *iterpool;
1879 svn_stream_puts(feedback_stream,
1880 _("\n-----Summary of corrupt revisions-----\n")));
1882 /* The standard column width for the revision number is 6 characters.
1883 If the revision number can potentially be larger (i.e. if end_revnum
1884 is larger than 1000000), we increase the column width as needed. */
1886 end_revnum = APR_ARRAY_IDX(verify_baton.error_summary,
1887 verify_baton.error_summary->nelts - 1,
1888 struct verification_error *)->rev;
1889 while (end_revnum >= 1000000)
1892 end_revnum = end_revnum / 10;
1895 iterpool = svn_pool_create(pool);
1896 for (i = 0; i < verify_baton.error_summary->nelts; i++)
1898 struct verification_error *verr;
1900 const char *rev_str;
1902 svn_pool_clear(iterpool);
1904 verr = APR_ARRAY_IDX(verify_baton.error_summary, i,
1905 struct verification_error *);
1907 if (verr->rev != SVN_INVALID_REVNUM)
1909 rev_str = apr_psprintf(iterpool, "r%ld", verr->rev);
1910 rev_str = apr_psprintf(iterpool, "%*s", rev_maxlength, rev_str);
1911 for (err = svn_error_purge_tracing(verr->err);
1912 err != SVN_NO_ERROR; err = err->child)
1915 const char *message;
1917 message = svn_err_best_message(err, buf, sizeof(buf));
1918 svn_error_clear(svn_stream_printf(feedback_stream, iterpool,
1920 rev_str, err->apr_err,
1926 svn_pool_destroy(iterpool);
1929 if (verify_baton.error_summary->nelts > 0)
1931 return svn_error_createf(SVN_ERR_CL_REPOS_VERIFY_FAILED, NULL,
1932 _("Failed to verify repository '%s'"),
1933 svn_dirent_local_style(
1934 opt_state->repository_path, pool));
1937 return SVN_NO_ERROR;
1940 /* This implements `svn_opt_subcommand_t'. */
1942 subcommand_hotcopy(apr_getopt_t *os, void *baton, apr_pool_t *pool)
1944 struct svnadmin_opt_state *opt_state = baton;
1945 svn_stream_t *feedback_stream = NULL;
1946 apr_array_header_t *targets;
1947 const char *new_repos_path;
1949 /* Expect one more argument: NEW_REPOS_PATH */
1950 SVN_ERR(parse_args(&targets, os, 1, 1, pool));
1951 new_repos_path = APR_ARRAY_IDX(targets, 0, const char *);
1952 SVN_ERR(target_arg_to_dirent(&new_repos_path, new_repos_path, pool));
1954 /* Progress feedback goes to STDOUT, unless they asked to suppress it. */
1955 if (! opt_state->quiet)
1956 feedback_stream = recode_stream_create(stdout, pool);
1958 return svn_repos_hotcopy3(opt_state->repository_path, new_repos_path,
1959 opt_state->clean_logs, opt_state->incremental,
1960 !opt_state->quiet ? repos_notify_handler : NULL,
1961 feedback_stream, check_cancel, NULL, pool);
1965 subcommand_info(apr_getopt_t *os, void *baton, apr_pool_t *pool)
1967 struct svnadmin_opt_state *opt_state = baton;
1973 /* Expect no more arguments. */
1974 SVN_ERR(parse_args(NULL, os, 0, 0, pool));
1976 SVN_ERR(open_repos(&repos, opt_state->repository_path, pool));
1977 fs = svn_repos_fs(repos);
1978 SVN_ERR(svn_cmdline_printf(pool, _("Path: %s\n"),
1979 svn_dirent_local_style(svn_repos_path(repos, pool),
1982 SVN_ERR(svn_fs_get_uuid(fs, &uuid, pool));
1983 SVN_ERR(svn_cmdline_printf(pool, _("UUID: %s\n"), uuid));
1985 int repos_format, minor;
1986 svn_version_t *repos_version, *fs_version;
1987 SVN_ERR(svn_repos_info_format(&repos_format, &repos_version,
1988 repos, pool, pool));
1989 SVN_ERR(svn_cmdline_printf(pool, _("Repository Format: %d\n"),
1992 SVN_ERR(svn_fs_info_format(&fs_format, &fs_version,
1994 /* fs_format will be printed later. */
1996 SVN_ERR_ASSERT(repos_version->major == SVN_VER_MAJOR);
1997 SVN_ERR_ASSERT(fs_version->major == SVN_VER_MAJOR);
1998 SVN_ERR_ASSERT(repos_version->patch == 0);
1999 SVN_ERR_ASSERT(fs_version->patch == 0);
2001 minor = (repos_version->minor > fs_version->minor)
2002 ? repos_version->minor : fs_version->minor;
2003 SVN_ERR(svn_cmdline_printf(pool, _("Compatible With Version: %d.%d.0\n"),
2004 SVN_VER_MAJOR, minor));
2008 apr_hash_t *capabilities_set;
2009 apr_array_header_t *capabilities;
2012 SVN_ERR(svn_repos_capabilities(&capabilities_set, repos, pool, pool));
2013 capabilities = svn_sort__hash(capabilities_set,
2014 svn_sort_compare_items_lexically,
2017 for (i = 0; i < capabilities->nelts; i++)
2019 svn_sort__item_t *item = &APR_ARRAY_IDX(capabilities, i,
2021 const char *capability = item->key;
2022 SVN_ERR(svn_cmdline_printf(pool, _("Repository Capability: %s\n"),
2028 const svn_fs_info_placeholder_t *info;
2030 SVN_ERR(svn_fs_info(&info, fs, pool, pool));
2031 SVN_ERR(svn_cmdline_printf(pool, _("Filesystem Type: %s\n"),
2033 SVN_ERR(svn_cmdline_printf(pool, _("Filesystem Format: %d\n"),
2035 if (!strcmp(info->fs_type, SVN_FS_TYPE_FSFS))
2037 const svn_fs_fsfs_info_t *fsfs_info = (const void *)info;
2038 svn_revnum_t youngest;
2039 SVN_ERR(svn_fs_youngest_rev(&youngest, fs, pool));
2041 if (fsfs_info->shard_size)
2042 SVN_ERR(svn_cmdline_printf(pool, _("FSFS Sharded: yes\n")));
2044 SVN_ERR(svn_cmdline_printf(pool, _("FSFS Sharded: no\n")));
2046 if (fsfs_info->shard_size)
2047 SVN_ERR(svn_cmdline_printf(pool, _("FSFS Shard Size: %d\n"),
2048 fsfs_info->shard_size));
2050 /* Print packing statistics, if enabled on the FS. */
2051 if (fsfs_info->shard_size)
2053 const int shard_size = fsfs_info->shard_size;
2054 const long shards_packed = fsfs_info->min_unpacked_rev / shard_size;
2055 const long shards_full = (youngest + 1) / shard_size;
2056 SVN_ERR(svn_cmdline_printf(pool, _("FSFS Shards Packed: %ld/%ld\n"),
2057 shards_packed, shards_full));
2060 if (fsfs_info->log_addressing)
2061 SVN_ERR(svn_cmdline_printf(pool, _("FSFS Logical Addressing: yes\n")));
2063 SVN_ERR(svn_cmdline_printf(pool, _("FSFS Logical Addressing: no\n")));
2068 apr_array_header_t *files;
2071 SVN_ERR(svn_fs_info_config_files(&files, fs, pool, pool));
2072 for (i = 0; i < files->nelts; i++)
2073 SVN_ERR(svn_cmdline_printf(pool, _("Configuration File: %s\n"),
2074 svn_dirent_local_style(
2075 APR_ARRAY_IDX(files, i, const char *),
2079 /* 'svn info' prints an extra newline here, to support multiple targets.
2080 We'll do the same. */
2081 SVN_ERR(svn_cmdline_printf(pool, "\n"));
2083 return SVN_NO_ERROR;
2086 /* This implements `svn_opt_subcommand_t'. */
2087 static svn_error_t *
2088 subcommand_lock(apr_getopt_t *os, void *baton, apr_pool_t *pool)
2090 struct svnadmin_opt_state *opt_state = baton;
2093 svn_fs_access_t *access;
2094 apr_array_header_t *args;
2095 const char *username;
2096 const char *lock_path;
2097 const char *comment_file_name;
2098 svn_stringbuf_t *file_contents;
2099 const char *lock_path_utf8;
2101 const char *lock_token = NULL;
2103 /* Expect three more arguments: PATH USERNAME COMMENT-FILE */
2104 SVN_ERR(parse_args(&args, os, 3, 4, pool));
2105 lock_path = APR_ARRAY_IDX(args, 0, const char *);
2106 username = APR_ARRAY_IDX(args, 1, const char *);
2107 comment_file_name = APR_ARRAY_IDX(args, 2, const char *);
2109 /* Expect one more optional argument: TOKEN */
2110 if (args->nelts == 4)
2111 lock_token = APR_ARRAY_IDX(args, 3, const char *);
2113 SVN_ERR(target_arg_to_dirent(&comment_file_name, comment_file_name, pool));
2115 SVN_ERR(open_repos(&repos, opt_state->repository_path, pool));
2116 fs = svn_repos_fs(repos);
2118 /* Create an access context describing the user. */
2119 SVN_ERR(svn_fs_create_access(&access, username, pool));
2121 /* Attach the access context to the filesystem. */
2122 SVN_ERR(svn_fs_set_access(fs, access));
2124 SVN_ERR(svn_stringbuf_from_file2(&file_contents, comment_file_name, pool));
2126 SVN_ERR(svn_utf_cstring_to_utf8(&lock_path_utf8, lock_path, pool));
2128 if (opt_state->bypass_hooks)
2129 SVN_ERR(svn_fs_lock(&lock, fs, lock_path_utf8,
2131 file_contents->data, /* comment */
2132 0, /* is_dav_comment */
2133 0, /* no expiration time. */
2137 SVN_ERR(svn_repos_fs_lock(&lock, repos, lock_path_utf8,
2139 file_contents->data, /* comment */
2140 0, /* is_dav_comment */
2141 0, /* no expiration time. */
2145 SVN_ERR(svn_cmdline_printf(pool, _("'%s' locked by user '%s'.\n"),
2146 lock_path, username));
2147 return SVN_NO_ERROR;
2150 static svn_error_t *
2151 subcommand_lslocks(apr_getopt_t *os, void *baton, apr_pool_t *pool)
2153 struct svnadmin_opt_state *opt_state = baton;
2154 apr_array_header_t *targets;
2156 const char *fs_path = "/";
2158 apr_hash_index_t *hi;
2159 apr_pool_t *iterpool = svn_pool_create(pool);
2161 SVN_ERR(svn_opt__args_to_target_array(&targets, os,
2162 apr_array_make(pool, 0,
2163 sizeof(const char *)),
2165 if (targets->nelts > 1)
2166 return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, 0,
2167 _("Too many arguments given"));
2169 fs_path = APR_ARRAY_IDX(targets, 0, const char *);
2171 SVN_ERR(open_repos(&repos, opt_state->repository_path, pool));
2173 /* Fetch all locks on or below the root directory. */
2174 SVN_ERR(svn_repos_fs_get_locks2(&locks, repos, fs_path, svn_depth_infinity,
2177 for (hi = apr_hash_first(pool, locks); hi; hi = apr_hash_next(hi))
2179 const char *cr_date, *exp_date = "";
2180 const char *path = apr_hash_this_key(hi);
2181 svn_lock_t *lock = apr_hash_this_val(hi);
2182 int comment_lines = 0;
2184 svn_pool_clear(iterpool);
2186 SVN_ERR(check_cancel(NULL));
2188 cr_date = svn_time_to_human_cstring(lock->creation_date, iterpool);
2190 if (lock->expiration_date)
2191 exp_date = svn_time_to_human_cstring(lock->expiration_date, iterpool);
2194 comment_lines = svn_cstring_count_newlines(lock->comment) + 1;
2196 SVN_ERR(svn_cmdline_printf(iterpool, _("Path: %s\n"), path));
2197 SVN_ERR(svn_cmdline_printf(iterpool, _("UUID Token: %s\n"), lock->token));
2198 SVN_ERR(svn_cmdline_printf(iterpool, _("Owner: %s\n"), lock->owner));
2199 SVN_ERR(svn_cmdline_printf(iterpool, _("Created: %s\n"), cr_date));
2200 SVN_ERR(svn_cmdline_printf(iterpool, _("Expires: %s\n"), exp_date));
2201 SVN_ERR(svn_cmdline_printf(iterpool,
2202 Q_("Comment (%i line):\n%s\n\n",
2203 "Comment (%i lines):\n%s\n\n",
2206 lock->comment ? lock->comment : ""));
2209 svn_pool_destroy(iterpool);
2211 return SVN_NO_ERROR;
2216 static svn_error_t *
2217 subcommand_rmlocks(apr_getopt_t *os, void *baton, apr_pool_t *pool)
2219 struct svnadmin_opt_state *opt_state = baton;
2222 svn_fs_access_t *access;
2224 apr_array_header_t *args;
2226 const char *username;
2227 apr_pool_t *subpool = svn_pool_create(pool);
2229 SVN_ERR(open_repos(&repos, opt_state->repository_path, pool));
2230 fs = svn_repos_fs(repos);
2232 /* svn_fs_unlock() demands that some username be associated with the
2233 filesystem, so just use the UID of the person running 'svnadmin'.*/
2234 username = svn_user_get_name(pool);
2236 username = "administrator";
2238 /* Create an access context describing the current user. */
2239 SVN_ERR(svn_fs_create_access(&access, username, pool));
2241 /* Attach the access context to the filesystem. */
2242 SVN_ERR(svn_fs_set_access(fs, access));
2244 /* Parse out any options. */
2245 SVN_ERR(svn_opt_parse_all_args(&args, os, pool));
2247 /* Our usage requires at least one FS path. */
2248 if (args->nelts == 0)
2249 return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, 0,
2250 _("No paths to unlock provided"));
2252 /* All the rest of the arguments are paths from which to remove locks. */
2253 for (i = 0; i < args->nelts; i++)
2255 const char *lock_path = APR_ARRAY_IDX(args, i, const char *);
2256 const char *lock_path_utf8;
2259 SVN_ERR(svn_utf_cstring_to_utf8(&lock_path_utf8, lock_path, subpool));
2261 /* Fetch the path's svn_lock_t. */
2262 err = svn_fs_get_lock(&lock, fs, lock_path_utf8, subpool);
2267 SVN_ERR(svn_cmdline_printf(subpool,
2268 _("Path '%s' isn't locked.\n"),
2273 /* Now forcibly destroy the lock. */
2274 err = svn_fs_unlock(fs, lock_path_utf8,
2275 lock->token, 1 /* force */, subpool);
2279 SVN_ERR(svn_cmdline_printf(subpool,
2280 _("Removed lock on '%s'.\n"), lock->path));
2285 /* Print the error, but move on to the next lock. */
2286 svn_handle_error2(err, stderr, FALSE /* non-fatal */, "svnadmin: ");
2287 svn_error_clear(err);
2290 svn_pool_clear(subpool);
2293 svn_pool_destroy(subpool);
2294 return SVN_NO_ERROR;
2298 /* This implements `svn_opt_subcommand_t'. */
2299 static svn_error_t *
2300 subcommand_unlock(apr_getopt_t *os, void *baton, apr_pool_t *pool)
2302 struct svnadmin_opt_state *opt_state = baton;
2305 svn_fs_access_t *access;
2306 apr_array_header_t *args;
2307 const char *username;
2308 const char *lock_path;
2309 const char *lock_path_utf8;
2310 const char *lock_token = NULL;
2312 /* Expect three more arguments: PATH USERNAME TOKEN */
2313 SVN_ERR(parse_args(&args, os, 3, 3, pool));
2314 lock_path = APR_ARRAY_IDX(args, 0, const char *);
2315 username = APR_ARRAY_IDX(args, 1, const char *);
2316 lock_token = APR_ARRAY_IDX(args, 2, const char *);
2318 /* Open the repos/FS, and associate an access context containing
2320 SVN_ERR(open_repos(&repos, opt_state->repository_path, pool));
2321 fs = svn_repos_fs(repos);
2322 SVN_ERR(svn_fs_create_access(&access, username, pool));
2323 SVN_ERR(svn_fs_set_access(fs, access));
2325 SVN_ERR(svn_utf_cstring_to_utf8(&lock_path_utf8, lock_path, pool));
2326 if (opt_state->bypass_hooks)
2327 SVN_ERR(svn_fs_unlock(fs, lock_path_utf8, lock_token,
2330 SVN_ERR(svn_repos_fs_unlock(repos, lock_path_utf8, lock_token,
2333 SVN_ERR(svn_cmdline_printf(pool, _("'%s' unlocked by user '%s'.\n"),
2334 lock_path, username));
2335 return SVN_NO_ERROR;
2339 /* This implements `svn_opt_subcommand_t'. */
2340 static svn_error_t *
2341 subcommand_upgrade(apr_getopt_t *os, void *baton, apr_pool_t *pool)
2344 struct svnadmin_opt_state *opt_state = baton;
2345 svn_stream_t *feedback_stream = NULL;
2347 /* Expect no more arguments. */
2348 SVN_ERR(parse_args(NULL, os, 0, 0, pool));
2350 SVN_ERR(svn_stream_for_stdout(&feedback_stream, pool));
2352 /* Restore default signal handlers. */
2353 setup_cancellation_signals(SIG_DFL);
2355 err = svn_repos_upgrade2(opt_state->repository_path, TRUE,
2356 repos_notify_handler, feedback_stream, pool);
2359 if (APR_STATUS_IS_EAGAIN(err->apr_err))
2361 svn_error_clear(err);
2363 if (! opt_state->wait)
2364 return svn_error_create(SVN_ERR_REPOS_LOCKED, NULL,
2365 _("Failed to get exclusive repository "
2366 "access; perhaps another process\n"
2367 "such as httpd, svnserve or svn "
2369 SVN_ERR(svn_cmdline_printf(pool,
2370 _("Waiting on repository lock; perhaps"
2371 " another process has it open?\n")));
2372 SVN_ERR(svn_cmdline_fflush(stdout));
2373 SVN_ERR(svn_repos_upgrade2(opt_state->repository_path, FALSE,
2374 repos_notify_handler, feedback_stream,
2377 else if (err->apr_err == SVN_ERR_FS_UNSUPPORTED_UPGRADE)
2379 return svn_error_quick_wrap(err,
2380 _("Upgrade of this repository's underlying versioned "
2381 "filesystem is not supported; consider "
2382 "dumping and loading the data elsewhere"));
2384 else if (err->apr_err == SVN_ERR_REPOS_UNSUPPORTED_UPGRADE)
2386 return svn_error_quick_wrap(err,
2387 _("Upgrade of this repository is not supported; consider "
2388 "dumping and loading the data elsewhere"));
2393 SVN_ERR(svn_cmdline_printf(pool, _("\nUpgrade completed.\n")));
2394 return SVN_NO_ERROR;
2398 /* This implements `svn_opt_subcommand_t'. */
2399 static svn_error_t *
2400 subcommand_delrevprop(apr_getopt_t *os, void *baton, apr_pool_t *pool)
2402 struct svnadmin_opt_state *opt_state = baton;
2403 apr_array_header_t *args;
2404 const char *prop_name;
2406 /* Expect one more argument: NAME */
2407 SVN_ERR(parse_args(&args, os, 1, 1, pool));
2408 prop_name = APR_ARRAY_IDX(args, 0, const char *);
2410 if (opt_state->txn_id)
2412 if (opt_state->start_revision.kind != svn_opt_revision_unspecified
2413 || opt_state->end_revision.kind != svn_opt_revision_unspecified)
2414 return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
2415 _("--revision (-r) and --transaction (-t) "
2416 "are mutually exclusive"));
2418 if (opt_state->use_pre_revprop_change_hook
2419 || opt_state->use_post_revprop_change_hook)
2420 return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
2421 _("Calling hooks is incompatible with "
2422 "--transaction (-t)"));
2424 else if (opt_state->start_revision.kind != svn_opt_revision_number)
2425 return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
2426 _("Missing revision"));
2427 else if (opt_state->end_revision.kind != svn_opt_revision_unspecified)
2428 return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
2429 _("Only one revision allowed"));
2431 return set_revprop(prop_name, NULL, opt_state, pool);
2439 * On success, leave *EXIT_CODE untouched and return SVN_NO_ERROR. On error,
2440 * either return an error to be displayed, or set *EXIT_CODE to non-zero and
2441 * return SVN_NO_ERROR.
2443 static svn_error_t *
2444 sub_main(int *exit_code, int argc, const char *argv[], apr_pool_t *pool)
2447 apr_status_t apr_err;
2449 const svn_opt_subcommand_desc2_t *subcommand = NULL;
2450 struct svnadmin_opt_state opt_state = { 0 };
2453 apr_array_header_t *received_opts;
2455 svn_boolean_t dash_F_arg = FALSE;
2457 received_opts = apr_array_make(pool, SVN_OPT_MAX_OPTIONS, sizeof(int));
2459 /* Check library versions */
2460 SVN_ERR(check_lib_versions());
2462 /* Initialize the FS library. */
2463 SVN_ERR(svn_fs_initialize(pool));
2467 SVN_ERR(subcommand_help(NULL, NULL, pool));
2468 *exit_code = EXIT_FAILURE;
2469 return SVN_NO_ERROR;
2472 /* Initialize opt_state. */
2473 opt_state.start_revision.kind = svn_opt_revision_unspecified;
2474 opt_state.end_revision.kind = svn_opt_revision_unspecified;
2475 opt_state.memory_cache_size = svn_cache_config_get()->cache_size;
2477 /* Parse options. */
2478 SVN_ERR(svn_cmdline__getopt_init(&os, argc, argv, pool));
2484 const char *opt_arg;
2485 const char *utf8_opt_arg;
2487 /* Parse the next option. */
2488 apr_err = apr_getopt_long(os, options_table, &opt_id, &opt_arg);
2489 if (APR_STATUS_IS_EOF(apr_err))
2493 SVN_ERR(subcommand_help(NULL, NULL, pool));
2494 *exit_code = EXIT_FAILURE;
2495 return SVN_NO_ERROR;
2498 /* Stash the option code in an array before parsing it. */
2499 APR_ARRAY_PUSH(received_opts, int) = opt_id;
2504 if (opt_state.start_revision.kind != svn_opt_revision_unspecified)
2506 return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
2507 _("Multiple revision arguments encountered; "
2508 "try '-r N:M' instead of '-r N -r M'"));
2510 if (svn_opt_parse_revision(&(opt_state.start_revision),
2511 &(opt_state.end_revision),
2512 opt_arg, pool) != 0)
2514 SVN_ERR(svn_utf_cstring_to_utf8(&utf8_opt_arg, opt_arg, pool));
2516 return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
2517 _("Syntax error in revision argument '%s'"),
2523 opt_state.txn_id = opt_arg;
2527 opt_state.quiet = TRUE;
2531 opt_state.help = TRUE;
2534 opt_state.memory_cache_size
2535 = 0x100000 * apr_strtoi64(opt_arg, NULL, 0);
2538 SVN_ERR(svn_utf_cstring_to_utf8(&utf8_opt_arg, opt_arg, pool));
2539 SVN_ERR(svn_stringbuf_from_file2(&(opt_state.filedata),
2540 utf8_opt_arg, pool));
2542 case svnadmin__version:
2543 opt_state.version = TRUE;
2545 case svnadmin__incremental:
2546 opt_state.incremental = TRUE;
2548 case svnadmin__deltas:
2549 opt_state.use_deltas = TRUE;
2551 case svnadmin__ignore_uuid:
2552 opt_state.uuid_action = svn_repos_load_uuid_ignore;
2554 case svnadmin__force_uuid:
2555 opt_state.uuid_action = svn_repos_load_uuid_force;
2557 case svnadmin__pre_1_4_compatible:
2558 opt_state.compatible_version = apr_pcalloc(pool, sizeof(svn_version_t));
2559 opt_state.compatible_version->major = 1;
2560 opt_state.compatible_version->minor = 3;
2562 case svnadmin__pre_1_5_compatible:
2563 opt_state.compatible_version = apr_pcalloc(pool, sizeof(svn_version_t));
2564 opt_state.compatible_version->major = 1;
2565 opt_state.compatible_version->minor = 4;
2567 case svnadmin__pre_1_6_compatible:
2568 opt_state.compatible_version = apr_pcalloc(pool, sizeof(svn_version_t));
2569 opt_state.compatible_version->major = 1;
2570 opt_state.compatible_version->minor = 5;
2572 case svnadmin__compatible_version:
2574 svn_version_t latest = { SVN_VER_MAJOR, SVN_VER_MINOR,
2575 SVN_VER_PATCH, NULL };
2576 svn_version_t *compatible_version;
2578 /* Parse the version string which carries our target
2580 SVN_ERR(svn_version__parse_version_string(&compatible_version,
2583 /* We can't create repository with a version older than 1.0.0. */
2584 if (! svn_version__at_least(compatible_version, 1, 0, 0))
2586 return svn_error_createf(SVN_ERR_UNSUPPORTED_FEATURE, NULL,
2587 _("Cannot create pre-1.0-compatible "
2591 /* We can't create repository with a version newer than what
2592 the running version of Subversion supports. */
2593 if (! svn_version__at_least(&latest,
2594 compatible_version->major,
2595 compatible_version->minor,
2596 compatible_version->patch))
2598 return svn_error_createf(SVN_ERR_UNSUPPORTED_FEATURE, NULL,
2599 _("Cannot guarantee compatibility "
2600 "beyond the current running version "
2605 opt_state.compatible_version = compatible_version;
2608 case svnadmin__keep_going:
2609 opt_state.keep_going = TRUE;
2611 case svnadmin__check_normalization:
2612 opt_state.check_normalization = TRUE;
2614 case svnadmin__metadata_only:
2615 opt_state.metadata_only = TRUE;
2617 case svnadmin__fs_type:
2618 SVN_ERR(svn_utf_cstring_to_utf8(&opt_state.fs_type, opt_arg, pool));
2620 case svnadmin__parent_dir:
2621 SVN_ERR(svn_utf_cstring_to_utf8(&opt_state.parent_dir, opt_arg,
2623 opt_state.parent_dir
2624 = svn_dirent_internal_style(opt_state.parent_dir, pool);
2626 case svnadmin__use_pre_commit_hook:
2627 opt_state.use_pre_commit_hook = TRUE;
2629 case svnadmin__use_post_commit_hook:
2630 opt_state.use_post_commit_hook = TRUE;
2632 case svnadmin__use_pre_revprop_change_hook:
2633 opt_state.use_pre_revprop_change_hook = TRUE;
2635 case svnadmin__use_post_revprop_change_hook:
2636 opt_state.use_post_revprop_change_hook = TRUE;
2638 case svnadmin__bdb_txn_nosync:
2639 opt_state.bdb_txn_nosync = TRUE;
2641 case svnadmin__bdb_log_keep:
2642 opt_state.bdb_log_keep = TRUE;
2644 case svnadmin__bypass_hooks:
2645 opt_state.bypass_hooks = TRUE;
2647 case svnadmin__bypass_prop_validation:
2648 opt_state.bypass_prop_validation = TRUE;
2650 case svnadmin__ignore_dates:
2651 opt_state.ignore_dates = TRUE;
2653 case svnadmin__clean_logs:
2654 opt_state.clean_logs = TRUE;
2656 case svnadmin__config_dir:
2657 SVN_ERR(svn_utf_cstring_to_utf8(&utf8_opt_arg, opt_arg, pool));
2658 opt_state.config_dir =
2659 apr_pstrdup(pool, svn_dirent_canonicalize(utf8_opt_arg, pool));
2661 case svnadmin__wait:
2662 opt_state.wait = TRUE;
2666 SVN_ERR(subcommand_help(NULL, NULL, pool));
2667 *exit_code = EXIT_FAILURE;
2668 return SVN_NO_ERROR;
2670 } /* close `switch' */
2671 } /* close `while' */
2673 /* If the user asked for help, then the rest of the arguments are
2674 the names of subcommands to get help on (if any), or else they're
2675 just typos/mistakes. Whatever the case, the subcommand to
2676 actually run is subcommand_help(). */
2678 subcommand = svn_opt_get_canonical_subcommand2(cmd_table, "help");
2680 /* If we're not running the `help' subcommand, then look for a
2681 subcommand in the first argument. */
2682 if (subcommand == NULL)
2684 if (os->ind >= os->argc)
2686 if (opt_state.version)
2688 /* Use the "help" subcommand to handle the "--version" option. */
2689 static const svn_opt_subcommand_desc2_t pseudo_cmd =
2690 { "--version", subcommand_help, {0}, "",
2691 {svnadmin__version, /* must accept its own option */
2695 subcommand = &pseudo_cmd;
2699 svn_error_clear(svn_cmdline_fprintf(stderr, pool,
2700 _("subcommand argument required\n")));
2701 SVN_ERR(subcommand_help(NULL, NULL, pool));
2702 *exit_code = EXIT_FAILURE;
2703 return SVN_NO_ERROR;
2708 const char *first_arg = os->argv[os->ind++];
2709 subcommand = svn_opt_get_canonical_subcommand2(cmd_table, first_arg);
2710 if (subcommand == NULL)
2712 const char *first_arg_utf8;
2713 SVN_ERR(svn_utf_cstring_to_utf8(&first_arg_utf8,
2716 svn_cmdline_fprintf(stderr, pool,
2717 _("Unknown subcommand: '%s'\n"),
2719 SVN_ERR(subcommand_help(NULL, NULL, pool));
2720 *exit_code = EXIT_FAILURE;
2721 return SVN_NO_ERROR;
2726 /* Every subcommand except `help' and `freeze' with '-F' require a
2727 second argument -- the repository path. Parse it out here and
2728 store it in opt_state. */
2729 if (!(subcommand->cmd_func == subcommand_help
2730 || (subcommand->cmd_func == subcommand_freeze && dash_F_arg)))
2732 const char *repos_path = NULL;
2734 if (os->ind >= os->argc)
2736 return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
2737 _("Repository argument required"));
2740 SVN_ERR(svn_utf_cstring_to_utf8(&repos_path, os->argv[os->ind++], pool));
2742 if (svn_path_is_url(repos_path))
2744 return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
2745 _("'%s' is a URL when it should be a "
2746 "local path"), repos_path);
2749 opt_state.repository_path = svn_dirent_internal_style(repos_path, pool);
2752 /* Check that the subcommand wasn't passed any inappropriate options. */
2753 for (i = 0; i < received_opts->nelts; i++)
2755 opt_id = APR_ARRAY_IDX(received_opts, i, int);
2757 /* All commands implicitly accept --help, so just skip over this
2758 when we see it. Note that we don't want to include this option
2759 in their "accepted options" list because it would be awfully
2760 redundant to display it in every commands' help text. */
2761 if (opt_id == 'h' || opt_id == '?')
2764 if (! svn_opt_subcommand_takes_option3(subcommand, opt_id, NULL))
2767 const apr_getopt_option_t *badopt =
2768 svn_opt_get_option_from_code2(opt_id, options_table, subcommand,
2770 svn_opt_format_option(&optstr, badopt, FALSE, pool);
2771 if (subcommand->name[0] == '-')
2772 SVN_ERR(subcommand_help(NULL, NULL, pool));
2774 svn_error_clear(svn_cmdline_fprintf(stderr, pool
2775 , _("Subcommand '%s' doesn't accept option '%s'\n"
2776 "Type 'svnadmin help %s' for usage.\n"),
2777 subcommand->name, optstr, subcommand->name));
2778 *exit_code = EXIT_FAILURE;
2779 return SVN_NO_ERROR;
2783 /* Set up our cancellation support. */
2784 setup_cancellation_signals(signal_handler);
2787 /* Disable SIGPIPE generation for the platforms that have it. */
2788 apr_signal(SIGPIPE, SIG_IGN);
2792 /* Disable SIGXFSZ generation for the platforms that have it, otherwise
2793 * working with large files when compiled against an APR that doesn't have
2794 * large file support will crash the program, which is uncool. */
2795 apr_signal(SIGXFSZ, SIG_IGN);
2798 /* Configure FSFS caches for maximum efficiency with svnadmin.
2799 * Also, apply the respective command line parameters, if given. */
2801 svn_cache_config_t settings = *svn_cache_config_get();
2803 settings.cache_size = opt_state.memory_cache_size;
2804 settings.single_threaded = TRUE;
2806 svn_cache_config_set(&settings);
2809 /* Run the subcommand. */
2810 err = (*subcommand->cmd_func)(os, &opt_state, pool);
2813 /* For argument-related problems, suggest using the 'help'
2815 if (err->apr_err == SVN_ERR_CL_INSUFFICIENT_ARGS
2816 || err->apr_err == SVN_ERR_CL_ARG_PARSING_ERROR)
2818 err = svn_error_quick_wrap(err,
2819 _("Try 'svnadmin help' for more info"));
2824 return SVN_NO_ERROR;
2828 main(int argc, const char *argv[])
2831 int exit_code = EXIT_SUCCESS;
2834 /* Initialize the app. */
2835 if (svn_cmdline_init("svnadmin", stderr) != EXIT_SUCCESS)
2836 return EXIT_FAILURE;
2838 /* Create our top-level pool. Use a separate mutexless allocator,
2839 * given this application is single threaded.
2841 pool = apr_allocator_owner_get(svn_pool_create_allocator(FALSE));
2843 err = sub_main(&exit_code, argc, argv, pool);
2845 /* Flush stdout and report if it fails. It would be flushed on exit anyway
2846 but this makes sure that output is not silently lost if it fails. */
2847 err = svn_error_compose_create(err, svn_cmdline_fflush(stdout));
2851 exit_code = EXIT_FAILURE;
2852 svn_cmdline_handle_exit_error(err, NULL, "svnadmin: ");
2855 svn_pool_destroy(pool);