summaryrefslogtreecommitdiff
path: root/subversion/svnadmin/svnadmin.c
diff options
context:
space:
mode:
Diffstat (limited to 'subversion/svnadmin/svnadmin.c')
-rw-r--r--subversion/svnadmin/svnadmin.c2362
1 files changed, 2362 insertions, 0 deletions
diff --git a/subversion/svnadmin/svnadmin.c b/subversion/svnadmin/svnadmin.c
new file mode 100644
index 0000000..cfbcf66
--- /dev/null
+++ b/subversion/svnadmin/svnadmin.c
@@ -0,0 +1,2362 @@
+/*
+ * svnadmin.c: Subversion server administration tool main file.
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+
+#include <apr_file_io.h>
+#include <apr_signal.h>
+
+#include "svn_hash.h"
+#include "svn_pools.h"
+#include "svn_cmdline.h"
+#include "svn_error.h"
+#include "svn_opt.h"
+#include "svn_utf.h"
+#include "svn_subst.h"
+#include "svn_dirent_uri.h"
+#include "svn_path.h"
+#include "svn_config.h"
+#include "svn_repos.h"
+#include "svn_cache_config.h"
+#include "svn_version.h"
+#include "svn_props.h"
+#include "svn_time.h"
+#include "svn_user.h"
+#include "svn_xml.h"
+
+#include "private/svn_opt_private.h"
+#include "private/svn_subr_private.h"
+#include "private/svn_cmdline_private.h"
+
+#include "svn_private_config.h"
+
+
+/*** Code. ***/
+
+/* A flag to see if we've been cancelled by the client or not. */
+static volatile sig_atomic_t cancelled = FALSE;
+
+/* A signal handler to support cancellation. */
+static void
+signal_handler(int signum)
+{
+ apr_signal(signum, SIG_IGN);
+ cancelled = TRUE;
+}
+
+
+/* A helper to set up the cancellation signal handlers. */
+static void
+setup_cancellation_signals(void (*handler)(int signum))
+{
+ apr_signal(SIGINT, handler);
+#ifdef SIGBREAK
+ /* SIGBREAK is a Win32 specific signal generated by ctrl-break. */
+ apr_signal(SIGBREAK, handler);
+#endif
+#ifdef SIGHUP
+ apr_signal(SIGHUP, handler);
+#endif
+#ifdef SIGTERM
+ apr_signal(SIGTERM, handler);
+#endif
+}
+
+
+/* Our cancellation callback. */
+static svn_error_t *
+check_cancel(void *baton)
+{
+ if (cancelled)
+ return svn_error_create(SVN_ERR_CANCELLED, NULL, _("Caught signal"));
+ else
+ return SVN_NO_ERROR;
+}
+
+
+/* Custom filesystem warning function. */
+static void
+warning_func(void *baton,
+ svn_error_t *err)
+{
+ if (! err)
+ return;
+ svn_handle_error2(err, stderr, FALSE, "svnadmin: ");
+}
+
+
+/* Helper to open a repository and set a warning func (so we don't
+ * SEGFAULT when libsvn_fs's default handler gets run). */
+static svn_error_t *
+open_repos(svn_repos_t **repos,
+ const char *path,
+ apr_pool_t *pool)
+{
+ /* construct FS configuration parameters: enable caches for r/o data */
+ apr_hash_t *fs_config = apr_hash_make(pool);
+ svn_hash_sets(fs_config, SVN_FS_CONFIG_FSFS_CACHE_DELTAS, "1");
+ svn_hash_sets(fs_config, SVN_FS_CONFIG_FSFS_CACHE_FULLTEXTS, "1");
+ svn_hash_sets(fs_config, SVN_FS_CONFIG_FSFS_CACHE_REVPROPS, "2");
+ svn_hash_sets(fs_config, SVN_FS_CONFIG_FSFS_CACHE_NS,
+ svn_uuid_generate(pool));
+
+ /* now, open the requested repository */
+ SVN_ERR(svn_repos_open2(repos, path, fs_config, pool));
+ svn_fs_set_warning_func(svn_repos_fs(*repos), warning_func, NULL);
+ return SVN_NO_ERROR;
+}
+
+
+/* Version compatibility check */
+static svn_error_t *
+check_lib_versions(void)
+{
+ static const svn_version_checklist_t checklist[] =
+ {
+ { "svn_subr", svn_subr_version },
+ { "svn_repos", svn_repos_version },
+ { "svn_fs", svn_fs_version },
+ { "svn_delta", svn_delta_version },
+ { NULL, NULL }
+ };
+ SVN_VERSION_DEFINE(my_version);
+
+ return svn_ver_check_list2(&my_version, checklist, svn_ver_equal);
+}
+
+
+
+/** Subcommands. **/
+
+static svn_opt_subcommand_t
+ subcommand_crashtest,
+ subcommand_create,
+ subcommand_deltify,
+ subcommand_dump,
+ subcommand_freeze,
+ subcommand_help,
+ subcommand_hotcopy,
+ subcommand_load,
+ subcommand_list_dblogs,
+ subcommand_list_unused_dblogs,
+ subcommand_lock,
+ subcommand_lslocks,
+ subcommand_lstxns,
+ subcommand_pack,
+ subcommand_recover,
+ subcommand_rmlocks,
+ subcommand_rmtxns,
+ subcommand_setlog,
+ subcommand_setrevprop,
+ subcommand_setuuid,
+ subcommand_unlock,
+ subcommand_upgrade,
+ subcommand_verify;
+
+enum svnadmin__cmdline_options_t
+ {
+ svnadmin__version = SVN_OPT_FIRST_LONGOPT_ID,
+ svnadmin__incremental,
+ svnadmin__deltas,
+ svnadmin__ignore_uuid,
+ svnadmin__force_uuid,
+ svnadmin__fs_type,
+ svnadmin__parent_dir,
+ svnadmin__bdb_txn_nosync,
+ svnadmin__bdb_log_keep,
+ svnadmin__config_dir,
+ svnadmin__bypass_hooks,
+ svnadmin__bypass_prop_validation,
+ svnadmin__use_pre_commit_hook,
+ svnadmin__use_post_commit_hook,
+ svnadmin__use_pre_revprop_change_hook,
+ svnadmin__use_post_revprop_change_hook,
+ svnadmin__clean_logs,
+ svnadmin__wait,
+ svnadmin__pre_1_4_compatible,
+ svnadmin__pre_1_5_compatible,
+ svnadmin__pre_1_6_compatible,
+ svnadmin__compatible_version
+ };
+
+/* Option codes and descriptions.
+ *
+ * The entire list must be terminated with an entry of nulls.
+ */
+static const apr_getopt_option_t options_table[] =
+ {
+ {"help", 'h', 0,
+ N_("show help on a subcommand")},
+
+ {NULL, '?', 0,
+ N_("show help on a subcommand")},
+
+ {"version", svnadmin__version, 0,
+ N_("show program version information")},
+
+ {"revision", 'r', 1,
+ N_("specify revision number ARG (or X:Y range)")},
+
+ {"transaction", 't', 1,
+ N_("specify transaction name ARG")},
+
+ {"incremental", svnadmin__incremental, 0,
+ N_("dump or hotcopy incrementally")},
+
+ {"deltas", svnadmin__deltas, 0,
+ N_("use deltas in dump output")},
+
+ {"bypass-hooks", svnadmin__bypass_hooks, 0,
+ N_("bypass the repository hook system")},
+
+ {"bypass-prop-validation", svnadmin__bypass_prop_validation, 0,
+ N_("bypass property validation logic")},
+
+ {"quiet", 'q', 0,
+ N_("no progress (only errors) to stderr")},
+
+ {"ignore-uuid", svnadmin__ignore_uuid, 0,
+ N_("ignore any repos UUID found in the stream")},
+
+ {"force-uuid", svnadmin__force_uuid, 0,
+ N_("set repos UUID to that found in stream, if any")},
+
+ {"fs-type", svnadmin__fs_type, 1,
+ N_("type of repository: 'fsfs' (default) or 'bdb'")},
+
+ {"parent-dir", svnadmin__parent_dir, 1,
+ N_("load at specified directory in repository")},
+
+ {"bdb-txn-nosync", svnadmin__bdb_txn_nosync, 0,
+ N_("disable fsync at transaction commit [Berkeley DB]")},
+
+ {"bdb-log-keep", svnadmin__bdb_log_keep, 0,
+ N_("disable automatic log file removal [Berkeley DB]")},
+
+ {"config-dir", svnadmin__config_dir, 1,
+ N_("read user configuration files from directory ARG")},
+
+ {"clean-logs", svnadmin__clean_logs, 0,
+ N_("remove redundant Berkeley DB log files\n"
+ " from source repository [Berkeley DB]")},
+
+ {"use-pre-commit-hook", svnadmin__use_pre_commit_hook, 0,
+ N_("call pre-commit hook before committing revisions")},
+
+ {"use-post-commit-hook", svnadmin__use_post_commit_hook, 0,
+ N_("call post-commit hook after committing revisions")},
+
+ {"use-pre-revprop-change-hook", svnadmin__use_pre_revprop_change_hook, 0,
+ N_("call hook before changing revision property")},
+
+ {"use-post-revprop-change-hook", svnadmin__use_post_revprop_change_hook, 0,
+ N_("call hook after changing revision property")},
+
+ {"wait", svnadmin__wait, 0,
+ N_("wait instead of exit if the repository is in\n"
+ " use by another process")},
+
+ {"pre-1.4-compatible", svnadmin__pre_1_4_compatible, 0,
+ N_("deprecated; see --compatible-version")},
+
+ {"pre-1.5-compatible", svnadmin__pre_1_5_compatible, 0,
+ N_("deprecated; see --compatible-version")},
+
+ {"pre-1.6-compatible", svnadmin__pre_1_6_compatible, 0,
+ N_("deprecated; see --compatible-version")},
+
+ {"memory-cache-size", 'M', 1,
+ N_("size of the extra in-memory cache in MB used to\n"
+ " minimize redundant operations. Default: 16.\n"
+ " [used for FSFS repositories only]")},
+
+ {"compatible-version", svnadmin__compatible_version, 1,
+ N_("use repository format compatible with Subversion\n"
+ " version ARG (\"1.5.5\", \"1.7\", etc.)")},
+
+ {"file", 'F', 1, N_("read repository paths from file ARG")},
+
+ {NULL}
+ };
+
+
+/* Array of available subcommands.
+ * The entire list must be terminated with an entry of nulls.
+ */
+static const svn_opt_subcommand_desc2_t cmd_table[] =
+{
+ {"crashtest", subcommand_crashtest, {0}, N_
+ ("usage: svnadmin crashtest REPOS_PATH\n\n"
+ "Open the repository at REPOS_PATH, then abort, thus simulating\n"
+ "a process that crashes while holding an open repository handle.\n"),
+ {0} },
+
+ {"create", subcommand_create, {0}, N_
+ ("usage: svnadmin create REPOS_PATH\n\n"
+ "Create a new, empty repository at REPOS_PATH.\n"),
+ {svnadmin__bdb_txn_nosync, svnadmin__bdb_log_keep,
+ svnadmin__config_dir, svnadmin__fs_type, svnadmin__compatible_version,
+ svnadmin__pre_1_4_compatible, svnadmin__pre_1_5_compatible,
+ svnadmin__pre_1_6_compatible
+ } },
+
+ {"deltify", subcommand_deltify, {0}, N_
+ ("usage: svnadmin deltify [-r LOWER[:UPPER]] REPOS_PATH\n\n"
+ "Run over the requested revision range, performing predecessor delti-\n"
+ "fication on the paths changed in those revisions. Deltification in\n"
+ "essence compresses the repository by only storing the differences or\n"
+ "delta from the preceding revision. If no revisions are specified,\n"
+ "this will simply deltify the HEAD revision.\n"),
+ {'r', 'q', 'M'} },
+
+ {"dump", subcommand_dump, {0}, N_
+ ("usage: svnadmin dump REPOS_PATH [-r LOWER[:UPPER] [--incremental]]\n\n"
+ "Dump the contents of filesystem to stdout in a 'dumpfile'\n"
+ "portable format, sending feedback to stderr. Dump revisions\n"
+ "LOWER rev through UPPER rev. If no revisions are given, dump all\n"
+ "revision trees. If only LOWER is given, dump that one revision tree.\n"
+ "If --incremental is passed, the first revision dumped will describe\n"
+ "only the paths changed in that revision; otherwise it will describe\n"
+ "every path present in the repository as of that revision. (In either\n"
+ "case, the second and subsequent revisions, if any, describe only paths\n"
+ "changed in those revisions.)\n"),
+ {'r', svnadmin__incremental, svnadmin__deltas, 'q', 'M'} },
+
+ {"freeze", subcommand_freeze, {0}, N_
+ ("usage: 1. svnadmin freeze REPOS_PATH PROGRAM [ARG...]\n"
+ " 2. svnadmin freeze -F FILE PROGRAM [ARG...]\n\n"
+ "1. Run PROGRAM passing ARGS while holding a write-lock on REPOS_PATH.\n"
+ "\n"
+ "2. Like 1 except all repositories listed in FILE are locked. The file\n"
+ " format is repository paths separated by newlines. Repositories are\n"
+ " locked in the same order as they are listed in the file.\n"),
+ {'F'} },
+
+ {"help", subcommand_help, {"?", "h"}, N_
+ ("usage: svnadmin help [SUBCOMMAND...]\n\n"
+ "Describe the usage of this program or its subcommands.\n"),
+ {0} },
+
+ {"hotcopy", subcommand_hotcopy, {0}, N_
+ ("usage: svnadmin hotcopy REPOS_PATH NEW_REPOS_PATH\n\n"
+ "Make a hot copy of a repository.\n"
+ "If --incremental is passed, data which already exists at the destination\n"
+ "is not copied again. Incremental mode is implemented for FSFS repositories.\n"),
+ {svnadmin__clean_logs, svnadmin__incremental} },
+
+ {"list-dblogs", subcommand_list_dblogs, {0}, N_
+ ("usage: svnadmin list-dblogs REPOS_PATH\n\n"
+ "List all Berkeley DB log files.\n\n"
+ "WARNING: Modifying or deleting logfiles which are still in use\n"
+ "will cause your repository to be corrupted.\n"),
+ {0} },
+
+ {"list-unused-dblogs", subcommand_list_unused_dblogs, {0}, N_
+ ("usage: svnadmin list-unused-dblogs REPOS_PATH\n\n"
+ "List unused Berkeley DB log files.\n\n"),
+ {0} },
+
+ {"load", subcommand_load, {0}, N_
+ ("usage: svnadmin load REPOS_PATH\n\n"
+ "Read a 'dumpfile'-formatted stream from stdin, committing\n"
+ "new revisions into the repository's filesystem. If the repository\n"
+ "was previously empty, its UUID will, by default, be changed to the\n"
+ "one specified in the stream. Progress feedback is sent to stdout.\n"
+ "If --revision is specified, limit the loaded revisions to only those\n"
+ "in the dump stream whose revision numbers match the specified range.\n"),
+ {'q', 'r', svnadmin__ignore_uuid, svnadmin__force_uuid,
+ svnadmin__use_pre_commit_hook, svnadmin__use_post_commit_hook,
+ svnadmin__parent_dir, svnadmin__bypass_prop_validation, 'M'} },
+
+ {"lock", subcommand_lock, {0}, N_
+ ("usage: svnadmin lock REPOS_PATH PATH USERNAME COMMENT-FILE [TOKEN]\n\n"
+ "Lock PATH by USERNAME setting comments from COMMENT-FILE.\n"
+ "If provided, use TOKEN as lock token. Use --bypass-hooks to avoid\n"
+ "triggering the pre-lock and post-lock hook scripts.\n"),
+ {svnadmin__bypass_hooks} },
+
+ {"lslocks", subcommand_lslocks, {0}, N_
+ ("usage: svnadmin lslocks REPOS_PATH [PATH-IN-REPOS]\n\n"
+ "Print descriptions of all locks on or under PATH-IN-REPOS (which,\n"
+ "if not provided, is the root of the repository).\n"),
+ {0} },
+
+ {"lstxns", subcommand_lstxns, {0}, N_
+ ("usage: svnadmin lstxns REPOS_PATH\n\n"
+ "Print the names of all uncommitted transactions.\n"),
+ {0} },
+
+ {"pack", subcommand_pack, {0}, N_
+ ("usage: svnadmin pack REPOS_PATH\n\n"
+ "Possibly compact the repository into a more efficient storage model.\n"
+ "This may not apply to all repositories, in which case, exit.\n"),
+ {'q'} },
+
+ {"recover", subcommand_recover, {0}, N_
+ ("usage: svnadmin recover REPOS_PATH\n\n"
+ "Run the recovery procedure on a repository. Do this if you've\n"
+ "been getting errors indicating that recovery ought to be run.\n"
+ "Berkeley DB recovery requires exclusive access and will\n"
+ "exit if the repository is in use by another process.\n"),
+ {svnadmin__wait} },
+
+ {"rmlocks", subcommand_rmlocks, {0}, N_
+ ("usage: svnadmin rmlocks REPOS_PATH LOCKED_PATH...\n\n"
+ "Unconditionally remove lock from each LOCKED_PATH.\n"),
+ {0} },
+
+ {"rmtxns", subcommand_rmtxns, {0}, N_
+ ("usage: svnadmin rmtxns REPOS_PATH TXN_NAME...\n\n"
+ "Delete the named transaction(s).\n"),
+ {'q'} },
+
+ {"setlog", subcommand_setlog, {0}, N_
+ ("usage: svnadmin setlog REPOS_PATH -r REVISION FILE\n\n"
+ "Set the log-message on revision REVISION to the contents of FILE. Use\n"
+ "--bypass-hooks to avoid triggering the revision-property-related hooks\n"
+ "(for example, if you do not want an email notification sent\n"
+ "from your post-revprop-change hook, or because the modification of\n"
+ "revision properties has not been enabled in the pre-revprop-change\n"
+ "hook).\n\n"
+ "NOTE: Revision properties are not versioned, so this command will\n"
+ "overwrite the previous log message.\n"),
+ {'r', svnadmin__bypass_hooks} },
+
+ {"setrevprop", subcommand_setrevprop, {0}, N_
+ ("usage: svnadmin setrevprop REPOS_PATH -r REVISION NAME FILE\n\n"
+ "Set the property NAME on revision REVISION to the contents of FILE. Use\n"
+ "--use-pre-revprop-change-hook/--use-post-revprop-change-hook to trigger\n"
+ "the revision property-related hooks (for example, if you want an email\n"
+ "notification sent from your post-revprop-change hook).\n\n"
+ "NOTE: Revision properties are not versioned, so this command will\n"
+ "overwrite the previous value of the property.\n"),
+ {'r', svnadmin__use_pre_revprop_change_hook,
+ svnadmin__use_post_revprop_change_hook} },
+
+ {"setuuid", subcommand_setuuid, {0}, N_
+ ("usage: svnadmin setuuid REPOS_PATH [NEW_UUID]\n\n"
+ "Reset the repository UUID for the repository located at REPOS_PATH. If\n"
+ "NEW_UUID is provided, use that as the new repository UUID; otherwise,\n"
+ "generate a brand new UUID for the repository.\n"),
+ {0} },
+
+ {"unlock", subcommand_unlock, {0}, N_
+ ("usage: svnadmin unlock REPOS_PATH LOCKED_PATH USERNAME TOKEN\n\n"
+ "Unlock LOCKED_PATH (as USERNAME) after verifying that the token\n"
+ "associated with the lock matches TOKEN. Use --bypass-hooks to avoid\n"
+ "triggering the pre-unlock and post-unlock hook scripts.\n"),
+ {svnadmin__bypass_hooks} },
+
+ {"upgrade", subcommand_upgrade, {0}, N_
+ ("usage: svnadmin upgrade REPOS_PATH\n\n"
+ "Upgrade the repository located at REPOS_PATH to the latest supported\n"
+ "schema version.\n\n"
+ "This functionality is provided as a convenience for repository\n"
+ "administrators who wish to make use of new Subversion functionality\n"
+ "without having to undertake a potentially costly full repository dump\n"
+ "and load operation. As such, the upgrade performs only the minimum\n"
+ "amount of work needed to accomplish this while still maintaining the\n"
+ "integrity of the repository. It does not guarantee the most optimized\n"
+ "repository state as a dump and subsequent load would.\n"),
+ {0} },
+
+ {"verify", subcommand_verify, {0}, N_
+ ("usage: svnadmin verify REPOS_PATH\n\n"
+ "Verify the data stored in the repository.\n"),
+ {'t', 'r', 'q', 'M'} },
+
+ { NULL, NULL, {0}, NULL, {0} }
+};
+
+
+/* Baton for passing option/argument state to a subcommand function. */
+struct svnadmin_opt_state
+{
+ const char *repository_path;
+ const char *fs_type; /* --fs-type */
+ svn_boolean_t pre_1_4_compatible; /* --pre-1.4-compatible */
+ svn_boolean_t pre_1_5_compatible; /* --pre-1.5-compatible */
+ svn_boolean_t pre_1_6_compatible; /* --pre-1.6-compatible */
+ svn_version_t *compatible_version; /* --compatible-version */
+ svn_opt_revision_t start_revision, end_revision; /* -r X[:Y] */
+ const char *txn_id; /* -t TXN */
+ svn_boolean_t help; /* --help or -? */
+ svn_boolean_t version; /* --version */
+ svn_boolean_t incremental; /* --incremental */
+ svn_boolean_t use_deltas; /* --deltas */
+ svn_boolean_t use_pre_commit_hook; /* --use-pre-commit-hook */
+ svn_boolean_t use_post_commit_hook; /* --use-post-commit-hook */
+ svn_boolean_t use_pre_revprop_change_hook; /* --use-pre-revprop-change-hook */
+ svn_boolean_t use_post_revprop_change_hook; /* --use-post-revprop-change-hook */
+ svn_boolean_t quiet; /* --quiet */
+ svn_boolean_t bdb_txn_nosync; /* --bdb-txn-nosync */
+ svn_boolean_t bdb_log_keep; /* --bdb-log-keep */
+ svn_boolean_t clean_logs; /* --clean-logs */
+ svn_boolean_t bypass_hooks; /* --bypass-hooks */
+ svn_boolean_t wait; /* --wait */
+ svn_boolean_t bypass_prop_validation; /* --bypass-prop-validation */
+ enum svn_repos_load_uuid uuid_action; /* --ignore-uuid,
+ --force-uuid */
+ apr_uint64_t memory_cache_size; /* --memory-cache-size M */
+ const char *parent_dir;
+ svn_stringbuf_t *filedata; /* --file */
+
+ const char *config_dir; /* Overriding Configuration Directory */
+};
+
+
+/* Set *REVNUM to the revision specified by REVISION (or to
+ SVN_INVALID_REVNUM if that has the type 'unspecified'),
+ possibly making use of the YOUNGEST revision number in REPOS. */
+static svn_error_t *
+get_revnum(svn_revnum_t *revnum, const svn_opt_revision_t *revision,
+ svn_revnum_t youngest, svn_repos_t *repos, apr_pool_t *pool)
+{
+ if (revision->kind == svn_opt_revision_number)
+ *revnum = revision->value.number;
+ else if (revision->kind == svn_opt_revision_head)
+ *revnum = youngest;
+ else if (revision->kind == svn_opt_revision_date)
+ SVN_ERR(svn_repos_dated_revision(revnum, repos, revision->value.date,
+ pool));
+ else if (revision->kind == svn_opt_revision_unspecified)
+ *revnum = SVN_INVALID_REVNUM;
+ else
+ return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
+ _("Invalid revision specifier"));
+
+ if (*revnum > youngest)
+ return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
+ _("Revisions must not be greater than the youngest revision (%ld)"),
+ youngest);
+
+ return SVN_NO_ERROR;
+}
+
+/* Set *PATH to an internal-style, UTF8-encoded, local dirent path
+ allocated from POOL and parsed from raw command-line argument ARG. */
+static svn_error_t *
+target_arg_to_dirent(const char **dirent,
+ const char *arg,
+ apr_pool_t *pool)
+{
+ const char *path;
+
+ SVN_ERR(svn_utf_cstring_to_utf8(&path, arg, pool));
+ if (svn_path_is_url(path))
+ return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
+ "Path '%s' is not a local path", path);
+ *dirent = svn_dirent_internal_style(path, pool);
+ return SVN_NO_ERROR;
+}
+
+/* Parse the remaining command-line arguments from OS, returning them
+ in a new array *ARGS (allocated from POOL) and optionally verifying
+ that we got the expected number thereof. If MIN_EXPECTED is not
+ negative, return an error if the function would return fewer than
+ MIN_EXPECTED arguments. If MAX_EXPECTED is not negative, return an
+ error if the function would return more than MAX_EXPECTED
+ arguments.
+
+ As a special case, when MIN_EXPECTED and MAX_EXPECTED are both 0,
+ allow ARGS to be NULL. */
+static svn_error_t *
+parse_args(apr_array_header_t **args,
+ apr_getopt_t *os,
+ int min_expected,
+ int max_expected,
+ apr_pool_t *pool)
+{
+ int num_args = os ? (os->argc - os->ind) : 0;
+
+ if (min_expected || max_expected)
+ SVN_ERR_ASSERT(args);
+
+ if ((min_expected >= 0) && (num_args < min_expected))
+ return svn_error_create(SVN_ERR_CL_INSUFFICIENT_ARGS, 0,
+ "Not enough arguments");
+ if ((max_expected >= 0) && (num_args > max_expected))
+ return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, 0,
+ "Too many arguments");
+ if (args)
+ {
+ *args = apr_array_make(pool, num_args, sizeof(const char *));
+
+ if (num_args)
+ while (os->ind < os->argc)
+ APR_ARRAY_PUSH(*args, const char *) =
+ apr_pstrdup(pool, os->argv[os->ind++]);
+ }
+
+ return SVN_NO_ERROR;
+}
+
+
+/* This implements `svn_opt_subcommand_t'. */
+static svn_error_t *
+subcommand_crashtest(apr_getopt_t *os, void *baton, apr_pool_t *pool)
+{
+ struct svnadmin_opt_state *opt_state = baton;
+ svn_repos_t *repos;
+
+ SVN_ERR(open_repos(&repos, opt_state->repository_path, pool));
+ SVN_ERR_MALFUNCTION();
+
+ /* merely silence a compiler warning (this will never be executed) */
+ return SVN_NO_ERROR;
+}
+
+/* This implements `svn_opt_subcommand_t'. */
+static svn_error_t *
+subcommand_create(apr_getopt_t *os, void *baton, apr_pool_t *pool)
+{
+ struct svnadmin_opt_state *opt_state = baton;
+ svn_repos_t *repos;
+ apr_hash_t *fs_config = apr_hash_make(pool);
+
+ /* Expect no more arguments. */
+ SVN_ERR(parse_args(NULL, os, 0, 0, pool));
+
+ svn_hash_sets(fs_config, SVN_FS_CONFIG_BDB_TXN_NOSYNC,
+ (opt_state->bdb_txn_nosync ? "1" :"0"));
+
+ svn_hash_sets(fs_config, SVN_FS_CONFIG_BDB_LOG_AUTOREMOVE,
+ (opt_state->bdb_log_keep ? "0" :"1"));
+
+ if (opt_state->fs_type)
+ {
+ /* With 1.8 we are announcing that BDB is deprecated. No support
+ * has been removed and it will continue to work until some future
+ * date. The purpose here is to discourage people from creating
+ * new BDB repositories which they will need to dump/load into
+ * FSFS or some new FS type in the future. */
+ if (0 == strcmp(opt_state->fs_type, SVN_FS_TYPE_BDB))
+ {
+ SVN_ERR(svn_cmdline_fprintf(
+ stderr, pool,
+ _("%swarning:"
+ " The \"%s\" repository back-end is deprecated,"
+ " consider using \"%s\" instead.\n"),
+ "svnadmin: ", SVN_FS_TYPE_BDB, SVN_FS_TYPE_FSFS));
+ fflush(stderr);
+ }
+ svn_hash_sets(fs_config, SVN_FS_CONFIG_FS_TYPE, opt_state->fs_type);
+ }
+
+ /* Prior to 1.8, we had explicit options to specify compatibility
+ with a handful of prior Subversion releases. */
+ if (opt_state->pre_1_4_compatible)
+ svn_hash_sets(fs_config, SVN_FS_CONFIG_PRE_1_4_COMPATIBLE, "1");
+ if (opt_state->pre_1_5_compatible)
+ svn_hash_sets(fs_config, SVN_FS_CONFIG_PRE_1_5_COMPATIBLE, "1");
+ if (opt_state->pre_1_6_compatible)
+ svn_hash_sets(fs_config, SVN_FS_CONFIG_PRE_1_6_COMPATIBLE, "1");
+
+ /* In 1.8, we figured out that we didn't have to keep extending this
+ madness indefinitely. */
+ if (opt_state->compatible_version)
+ {
+ if (! svn_version__at_least(opt_state->compatible_version, 1, 4, 0))
+ svn_hash_sets(fs_config, SVN_FS_CONFIG_PRE_1_4_COMPATIBLE, "1");
+ if (! svn_version__at_least(opt_state->compatible_version, 1, 5, 0))
+ svn_hash_sets(fs_config, SVN_FS_CONFIG_PRE_1_5_COMPATIBLE, "1");
+ if (! svn_version__at_least(opt_state->compatible_version, 1, 6, 0))
+ svn_hash_sets(fs_config, SVN_FS_CONFIG_PRE_1_6_COMPATIBLE, "1");
+ if (! svn_version__at_least(opt_state->compatible_version, 1, 8, 0))
+ svn_hash_sets(fs_config, SVN_FS_CONFIG_PRE_1_8_COMPATIBLE, "1");
+ }
+
+ if (opt_state->compatible_version
+ && ! svn_version__at_least(opt_state->compatible_version, 1, 1, 0)
+ /* ### TODO: this NULL check hard-codes knowledge of the library's
+ default fs-type value */
+ && (opt_state->fs_type == NULL
+ || !strcmp(opt_state->fs_type, SVN_FS_TYPE_FSFS)))
+ {
+ return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
+ _("Repositories compatible with 1.0.x must use "
+ "--fs-type=bdb"));
+ }
+
+ SVN_ERR(svn_repos_create(&repos, opt_state->repository_path,
+ NULL, NULL, NULL, fs_config, pool));
+ svn_fs_set_warning_func(svn_repos_fs(repos), warning_func, NULL);
+ return SVN_NO_ERROR;
+}
+
+
+/* This implements `svn_opt_subcommand_t'. */
+static svn_error_t *
+subcommand_deltify(apr_getopt_t *os, void *baton, apr_pool_t *pool)
+{
+ struct svnadmin_opt_state *opt_state = baton;
+ svn_repos_t *repos;
+ svn_fs_t *fs;
+ svn_revnum_t start = SVN_INVALID_REVNUM, end = SVN_INVALID_REVNUM;
+ svn_revnum_t youngest, revision;
+ apr_pool_t *subpool = svn_pool_create(pool);
+
+ /* Expect no more arguments. */
+ SVN_ERR(parse_args(NULL, os, 0, 0, pool));
+
+ SVN_ERR(open_repos(&repos, opt_state->repository_path, pool));
+ fs = svn_repos_fs(repos);
+ SVN_ERR(svn_fs_youngest_rev(&youngest, fs, pool));
+
+ /* Find the revision numbers at which to start and end. */
+ SVN_ERR(get_revnum(&start, &opt_state->start_revision,
+ youngest, repos, pool));
+ SVN_ERR(get_revnum(&end, &opt_state->end_revision,
+ youngest, repos, pool));
+
+ /* Fill in implied revisions if necessary. */
+ if (start == SVN_INVALID_REVNUM)
+ start = youngest;
+ if (end == SVN_INVALID_REVNUM)
+ end = start;
+
+ if (start > end)
+ return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
+ _("First revision cannot be higher than second"));
+
+ /* Loop over the requested revision range, performing the
+ predecessor deltification on paths changed in each. */
+ for (revision = start; revision <= end; revision++)
+ {
+ svn_pool_clear(subpool);
+ SVN_ERR(check_cancel(NULL));
+ if (! opt_state->quiet)
+ SVN_ERR(svn_cmdline_printf(subpool, _("Deltifying revision %ld..."),
+ revision));
+ SVN_ERR(svn_fs_deltify_revision(fs, revision, subpool));
+ if (! opt_state->quiet)
+ SVN_ERR(svn_cmdline_printf(subpool, _("done.\n")));
+ }
+ svn_pool_destroy(subpool);
+
+ return SVN_NO_ERROR;
+}
+
+
+/* Implementation of svn_repos_notify_func_t to wrap the output to a
+ response stream for svn_repos_dump_fs2() and svn_repos_verify_fs() */
+static void
+repos_notify_handler(void *baton,
+ const svn_repos_notify_t *notify,
+ apr_pool_t *scratch_pool)
+{
+ svn_stream_t *feedback_stream = baton;
+ apr_size_t len;
+
+ switch (notify->action)
+ {
+ case svn_repos_notify_warning:
+ svn_error_clear(svn_stream_printf(feedback_stream, scratch_pool,
+ "WARNING 0x%04x: %s\n", notify->warning,
+ notify->warning_str));
+ return;
+
+ case svn_repos_notify_dump_rev_end:
+ svn_error_clear(svn_stream_printf(feedback_stream, scratch_pool,
+ _("* Dumped revision %ld.\n"),
+ notify->revision));
+ return;
+
+ case svn_repos_notify_verify_rev_end:
+ svn_error_clear(svn_stream_printf(feedback_stream, scratch_pool,
+ _("* Verified revision %ld.\n"),
+ notify->revision));
+ return;
+
+ case svn_repos_notify_verify_rev_structure:
+ if (notify->revision == SVN_INVALID_REVNUM)
+ svn_error_clear(svn_stream_printf(feedback_stream, scratch_pool,
+ _("* Verifying repository metadata ...\n")));
+ else
+ svn_error_clear(svn_stream_printf(feedback_stream, scratch_pool,
+ _("* Verifying metadata at revision %ld ...\n"),
+ notify->revision));
+ return;
+
+ case svn_repos_notify_pack_shard_start:
+ {
+ const char *shardstr = apr_psprintf(scratch_pool,
+ "%" APR_INT64_T_FMT,
+ notify->shard);
+ svn_error_clear(svn_stream_printf(feedback_stream, scratch_pool,
+ _("Packing revisions in shard %s..."),
+ shardstr));
+ }
+ return;
+
+ case svn_repos_notify_pack_shard_end:
+ svn_error_clear(svn_stream_puts(feedback_stream, _("done.\n")));
+ return;
+
+ case svn_repos_notify_pack_shard_start_revprop:
+ {
+ const char *shardstr = apr_psprintf(scratch_pool,
+ "%" APR_INT64_T_FMT,
+ notify->shard);
+ svn_error_clear(svn_stream_printf(feedback_stream, scratch_pool,
+ _("Packing revprops in shard %s..."),
+ shardstr));
+ }
+ return;
+
+ case svn_repos_notify_pack_shard_end_revprop:
+ svn_error_clear(svn_stream_puts(feedback_stream, _("done.\n")));
+ return;
+
+ case svn_repos_notify_load_txn_committed:
+ if (notify->old_revision == SVN_INVALID_REVNUM)
+ {
+ svn_error_clear(svn_stream_printf(feedback_stream, scratch_pool,
+ _("\n------- Committed revision %ld >>>\n\n"),
+ notify->new_revision));
+ }
+ else
+ {
+ svn_error_clear(svn_stream_printf(feedback_stream, scratch_pool,
+ _("\n------- Committed new rev %ld"
+ " (loaded from original rev %ld"
+ ") >>>\n\n"), notify->new_revision,
+ notify->old_revision));
+ }
+ return;
+
+ case svn_repos_notify_load_node_start:
+ {
+ switch (notify->node_action)
+ {
+ case svn_node_action_change:
+ svn_error_clear(svn_stream_printf(feedback_stream, scratch_pool,
+ _(" * editing path : %s ..."),
+ notify->path));
+ break;
+
+ case svn_node_action_delete:
+ svn_error_clear(svn_stream_printf(feedback_stream, scratch_pool,
+ _(" * deleting path : %s ..."),
+ notify->path));
+ break;
+
+ case svn_node_action_add:
+ svn_error_clear(svn_stream_printf(feedback_stream, scratch_pool,
+ _(" * adding path : %s ..."),
+ notify->path));
+ break;
+
+ case svn_node_action_replace:
+ svn_error_clear(svn_stream_printf(feedback_stream, scratch_pool,
+ _(" * replacing path : %s ..."),
+ notify->path));
+ break;
+
+ }
+ }
+ return;
+
+ case svn_repos_notify_load_node_done:
+ svn_error_clear(svn_stream_printf(feedback_stream, scratch_pool,
+ "%s", _(" done.\n")));
+ return;
+
+ case svn_repos_notify_load_copied_node:
+ len = 9;
+ svn_error_clear(svn_stream_write(feedback_stream, "COPIED...", &len));
+ return;
+
+ case svn_repos_notify_load_txn_start:
+ svn_error_clear(svn_stream_printf(feedback_stream, scratch_pool,
+ _("<<< Started new transaction, based on "
+ "original revision %ld\n"),
+ notify->old_revision));
+ return;
+
+ case svn_repos_notify_load_skipped_rev:
+ svn_error_clear(svn_stream_printf(feedback_stream, scratch_pool,
+ _("<<< Skipped original revision %ld\n"),
+ notify->old_revision));
+ return;
+
+ case svn_repos_notify_load_normalized_mergeinfo:
+ svn_error_clear(svn_stream_printf(feedback_stream, scratch_pool,
+ _(" removing '\\r' from %s ..."),
+ SVN_PROP_MERGEINFO));
+ return;
+
+ case svn_repos_notify_mutex_acquired:
+ /* Enable cancellation signal handlers. */
+ setup_cancellation_signals(signal_handler);
+ return;
+
+ case svn_repos_notify_recover_start:
+ svn_error_clear(svn_stream_printf(feedback_stream, scratch_pool,
+ _("Repository lock acquired.\n"
+ "Please wait; recovering the"
+ " repository may take some time...\n")));
+ return;
+
+ case svn_repos_notify_upgrade_start:
+ svn_error_clear(svn_stream_puts(feedback_stream,
+ _("Repository lock acquired.\n"
+ "Please wait; upgrading the"
+ " repository may take some time...\n")));
+ return;
+
+ default:
+ return;
+ }
+}
+
+
+/* Baton for recode_write(). */
+struct recode_write_baton
+{
+ apr_pool_t *pool;
+ FILE *out;
+};
+
+/* This implements the 'svn_write_fn_t' interface.
+
+ Write DATA to ((struct recode_write_baton *) BATON)->out, in the
+ console encoding, using svn_cmdline_fprintf(). DATA is a
+ UTF8-encoded C string, therefore ignore LEN.
+
+ ### This recoding mechanism might want to be abstracted into
+ ### svn_io.h or svn_cmdline.h, if it proves useful elsewhere. */
+static svn_error_t *recode_write(void *baton,
+ const char *data,
+ apr_size_t *len)
+{
+ struct recode_write_baton *rwb = baton;
+ svn_pool_clear(rwb->pool);
+ return svn_cmdline_fputs(data, rwb->out, rwb->pool);
+}
+
+/* Create a stream, to write to STD_STREAM, that uses recode_write()
+ to perform UTF-8 to console encoding translation. */
+static svn_stream_t *
+recode_stream_create(FILE *std_stream, apr_pool_t *pool)
+{
+ struct recode_write_baton *std_stream_rwb =
+ apr_palloc(pool, sizeof(struct recode_write_baton));
+
+ svn_stream_t *rw_stream = svn_stream_create(std_stream_rwb, pool);
+ std_stream_rwb->pool = svn_pool_create(pool);
+ std_stream_rwb->out = std_stream;
+ svn_stream_set_write(rw_stream, recode_write);
+ return rw_stream;
+}
+
+
+/* This implements `svn_opt_subcommand_t'. */
+static svn_error_t *
+subcommand_dump(apr_getopt_t *os, void *baton, apr_pool_t *pool)
+{
+ struct svnadmin_opt_state *opt_state = baton;
+ svn_repos_t *repos;
+ svn_fs_t *fs;
+ svn_stream_t *stdout_stream;
+ svn_revnum_t lower = SVN_INVALID_REVNUM, upper = SVN_INVALID_REVNUM;
+ svn_revnum_t youngest;
+ svn_stream_t *progress_stream = NULL;
+
+ /* Expect no more arguments. */
+ SVN_ERR(parse_args(NULL, os, 0, 0, pool));
+
+ SVN_ERR(open_repos(&repos, opt_state->repository_path, pool));
+ fs = svn_repos_fs(repos);
+ SVN_ERR(svn_fs_youngest_rev(&youngest, fs, pool));
+
+ /* Find the revision numbers at which to start and end. */
+ SVN_ERR(get_revnum(&lower, &opt_state->start_revision,
+ youngest, repos, pool));
+ SVN_ERR(get_revnum(&upper, &opt_state->end_revision,
+ youngest, repos, pool));
+
+ /* Fill in implied revisions if necessary. */
+ if (lower == SVN_INVALID_REVNUM)
+ {
+ lower = 0;
+ upper = youngest;
+ }
+ else if (upper == SVN_INVALID_REVNUM)
+ {
+ upper = lower;
+ }
+
+ if (lower > upper)
+ return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
+ _("First revision cannot be higher than second"));
+
+ SVN_ERR(svn_stream_for_stdout(&stdout_stream, pool));
+
+ /* Progress feedback goes to STDERR, unless they asked to suppress it. */
+ if (! opt_state->quiet)
+ progress_stream = recode_stream_create(stderr, pool);
+
+ SVN_ERR(svn_repos_dump_fs3(repos, stdout_stream, lower, upper,
+ opt_state->incremental, opt_state->use_deltas,
+ !opt_state->quiet ? repos_notify_handler : NULL,
+ progress_stream, check_cancel, NULL, pool));
+
+ return SVN_NO_ERROR;
+}
+
+struct freeze_baton_t {
+ const char *command;
+ const char **args;
+ int status;
+};
+
+/* Implements svn_repos_freeze_func_t */
+static svn_error_t *
+freeze_body(void *baton,
+ apr_pool_t *pool)
+{
+ struct freeze_baton_t *b = baton;
+ apr_status_t apr_err;
+ apr_file_t *infile, *outfile, *errfile;
+
+ apr_err = apr_file_open_stdin(&infile, pool);
+ if (apr_err)
+ return svn_error_wrap_apr(apr_err, "Can't open stdin");
+ apr_err = apr_file_open_stdout(&outfile, pool);
+ if (apr_err)
+ return svn_error_wrap_apr(apr_err, "Can't open stdout");
+ apr_err = apr_file_open_stderr(&errfile, pool);
+ if (apr_err)
+ return svn_error_wrap_apr(apr_err, "Can't open stderr");
+
+ SVN_ERR(svn_io_run_cmd(NULL, b->command, b->args, &b->status,
+ NULL, TRUE,
+ infile, outfile, errfile, pool));
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+subcommand_freeze(apr_getopt_t *os, void *baton, apr_pool_t *pool)
+{
+ struct svnadmin_opt_state *opt_state = baton;
+ apr_array_header_t *paths;
+ apr_array_header_t *args;
+ int i;
+ struct freeze_baton_t b;
+
+ SVN_ERR(svn_opt_parse_all_args(&args, os, pool));
+
+ if (!args->nelts)
+ return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, 0,
+ _("No program provided"));
+
+ if (!opt_state->filedata)
+ {
+ /* One repository on the command line. */
+ paths = apr_array_make(pool, 1, sizeof(const char *));
+ APR_ARRAY_PUSH(paths, const char *) = opt_state->repository_path;
+ }
+ else
+ {
+ /* All repositories in filedata. */
+ paths = svn_cstring_split(opt_state->filedata->data, "\n", FALSE, pool);
+ }
+
+ b.command = APR_ARRAY_IDX(args, 0, const char *);
+ b.args = apr_palloc(pool, sizeof(char *) * (args->nelts + 1));
+ for (i = 0; i < args->nelts; ++i)
+ b.args[i] = APR_ARRAY_IDX(args, i, const char *);
+ b.args[args->nelts] = NULL;
+
+ SVN_ERR(svn_repos_freeze(paths, freeze_body, &b, pool));
+
+ /* Make any non-zero status visible to the user. */
+ if (b.status)
+ exit(b.status);
+
+ return SVN_NO_ERROR;
+}
+
+
+/* This implements `svn_opt_subcommand_t'. */
+static svn_error_t *
+subcommand_help(apr_getopt_t *os, void *baton, apr_pool_t *pool)
+{
+ struct svnadmin_opt_state *opt_state = baton;
+ const char *header =
+ _("general usage: svnadmin SUBCOMMAND REPOS_PATH [ARGS & OPTIONS ...]\n"
+ "Type 'svnadmin help <subcommand>' for help on a specific subcommand.\n"
+ "Type 'svnadmin --version' to see the program version and FS modules.\n"
+ "\n"
+ "Available subcommands:\n");
+
+ const char *fs_desc_start
+ = _("The following repository back-end (FS) modules are available:\n\n");
+
+ svn_stringbuf_t *version_footer;
+
+ version_footer = svn_stringbuf_create(fs_desc_start, pool);
+ SVN_ERR(svn_fs_print_modules(version_footer, pool));
+
+ SVN_ERR(svn_opt_print_help4(os, "svnadmin",
+ opt_state ? opt_state->version : FALSE,
+ opt_state ? opt_state->quiet : FALSE,
+ /*###opt_state ? opt_state->verbose :*/ FALSE,
+ version_footer->data,
+ header, cmd_table, options_table, NULL, NULL,
+ pool));
+
+ return SVN_NO_ERROR;
+}
+
+
+/* Set *REVNUM to the revision number of a numeric REV, or to
+ SVN_INVALID_REVNUM if REV is unspecified. */
+static svn_error_t *
+optrev_to_revnum(svn_revnum_t *revnum, const svn_opt_revision_t *opt_rev)
+{
+ if (opt_rev->kind == svn_opt_revision_number)
+ {
+ *revnum = opt_rev->value.number;
+ if (! SVN_IS_VALID_REVNUM(*revnum))
+ return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
+ _("Invalid revision number (%ld) specified"),
+ *revnum);
+ }
+ else if (opt_rev->kind == svn_opt_revision_unspecified)
+ {
+ *revnum = SVN_INVALID_REVNUM;
+ }
+ else
+ {
+ return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
+ _("Non-numeric revision specified"));
+ }
+ return SVN_NO_ERROR;
+}
+
+
+/* This implements `svn_opt_subcommand_t'. */
+static svn_error_t *
+subcommand_load(apr_getopt_t *os, void *baton, apr_pool_t *pool)
+{
+ svn_error_t *err;
+ struct svnadmin_opt_state *opt_state = baton;
+ svn_repos_t *repos;
+ svn_revnum_t lower = SVN_INVALID_REVNUM, upper = SVN_INVALID_REVNUM;
+ svn_stream_t *stdin_stream, *stdout_stream = NULL;
+
+ /* Expect no more arguments. */
+ SVN_ERR(parse_args(NULL, os, 0, 0, pool));
+
+ /* Find the revision numbers at which to start and end. We only
+ support a limited set of revision kinds: number and unspecified. */
+ SVN_ERR(optrev_to_revnum(&lower, &opt_state->start_revision));
+ SVN_ERR(optrev_to_revnum(&upper, &opt_state->end_revision));
+
+ /* Fill in implied revisions if necessary. */
+ if ((upper == SVN_INVALID_REVNUM) && (lower != SVN_INVALID_REVNUM))
+ {
+ upper = lower;
+ }
+ else if ((upper != SVN_INVALID_REVNUM) && (lower == SVN_INVALID_REVNUM))
+ {
+ lower = upper;
+ }
+
+ /* Ensure correct range ordering. */
+ if (lower > upper)
+ {
+ return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
+ _("First revision cannot be higher than second"));
+ }
+
+ SVN_ERR(open_repos(&repos, opt_state->repository_path, pool));
+
+ /* Read the stream from STDIN. Users can redirect a file. */
+ SVN_ERR(svn_stream_for_stdin(&stdin_stream, pool));
+
+ /* Progress feedback goes to STDOUT, unless they asked to suppress it. */
+ if (! opt_state->quiet)
+ stdout_stream = recode_stream_create(stdout, pool);
+
+ err = svn_repos_load_fs4(repos, stdin_stream, lower, upper,
+ opt_state->uuid_action, opt_state->parent_dir,
+ opt_state->use_pre_commit_hook,
+ opt_state->use_post_commit_hook,
+ !opt_state->bypass_prop_validation,
+ opt_state->quiet ? NULL : repos_notify_handler,
+ stdout_stream, check_cancel, NULL, pool);
+ if (err && err->apr_err == SVN_ERR_BAD_PROPERTY_VALUE)
+ return svn_error_quick_wrap(err,
+ _("Invalid property value found in "
+ "dumpstream; consider repairing the source "
+ "or using --bypass-prop-validation while "
+ "loading."));
+ return err;
+}
+
+
+/* This implements `svn_opt_subcommand_t'. */
+static svn_error_t *
+subcommand_lstxns(apr_getopt_t *os, void *baton, apr_pool_t *pool)
+{
+ struct svnadmin_opt_state *opt_state = baton;
+ svn_repos_t *repos;
+ svn_fs_t *fs;
+ apr_array_header_t *txns;
+ int i;
+
+ /* Expect no more arguments. */
+ SVN_ERR(parse_args(NULL, os, 0, 0, pool));
+
+ SVN_ERR(open_repos(&repos, opt_state->repository_path, pool));
+ fs = svn_repos_fs(repos);
+ SVN_ERR(svn_fs_list_transactions(&txns, fs, pool));
+
+ /* Loop, printing revisions. */
+ for (i = 0; i < txns->nelts; i++)
+ {
+ SVN_ERR(svn_cmdline_printf(pool, "%s\n",
+ APR_ARRAY_IDX(txns, i, const char *)));
+ }
+
+ return SVN_NO_ERROR;
+}
+
+
+/* This implements `svn_opt_subcommand_t'. */
+static svn_error_t *
+subcommand_recover(apr_getopt_t *os, void *baton, apr_pool_t *pool)
+{
+ svn_revnum_t youngest_rev;
+ svn_repos_t *repos;
+ svn_error_t *err;
+ struct svnadmin_opt_state *opt_state = baton;
+ svn_stream_t *stdout_stream;
+
+ /* Expect no more arguments. */
+ SVN_ERR(parse_args(NULL, os, 0, 0, pool));
+
+ SVN_ERR(svn_stream_for_stdout(&stdout_stream, pool));
+
+ /* Restore default signal handlers until after we have acquired the
+ * exclusive lock so that the user interrupt before we actually
+ * touch the repository. */
+ setup_cancellation_signals(SIG_DFL);
+
+ err = svn_repos_recover4(opt_state->repository_path, TRUE,
+ repos_notify_handler, stdout_stream,
+ check_cancel, NULL, pool);
+ if (err)
+ {
+ if (! APR_STATUS_IS_EAGAIN(err->apr_err))
+ return err;
+ svn_error_clear(err);
+ if (! opt_state->wait)
+ return svn_error_create(SVN_ERR_REPOS_LOCKED, NULL,
+ _("Failed to get exclusive repository "
+ "access; perhaps another process\n"
+ "such as httpd, svnserve or svn "
+ "has it open?"));
+ SVN_ERR(svn_cmdline_printf(pool,
+ _("Waiting on repository lock; perhaps"
+ " another process has it open?\n")));
+ SVN_ERR(svn_cmdline_fflush(stdout));
+ SVN_ERR(svn_repos_recover4(opt_state->repository_path, FALSE,
+ repos_notify_handler, stdout_stream,
+ check_cancel, NULL, pool));
+ }
+
+ SVN_ERR(svn_cmdline_printf(pool, _("\nRecovery completed.\n")));
+
+ /* Since db transactions may have been replayed, it's nice to tell
+ people what the latest revision is. It also proves that the
+ recovery actually worked. */
+ SVN_ERR(open_repos(&repos, opt_state->repository_path, pool));
+ SVN_ERR(svn_fs_youngest_rev(&youngest_rev, svn_repos_fs(repos), pool));
+ SVN_ERR(svn_cmdline_printf(pool, _("The latest repos revision is %ld.\n"),
+ youngest_rev));
+
+ return SVN_NO_ERROR;
+}
+
+
+/* This implements `svn_opt_subcommand_t'. */
+static svn_error_t *
+list_dblogs(apr_getopt_t *os, void *baton, svn_boolean_t only_unused,
+ apr_pool_t *pool)
+{
+ struct svnadmin_opt_state *opt_state = baton;
+ apr_array_header_t *logfiles;
+ int i;
+
+ /* Expect no more arguments. */
+ SVN_ERR(parse_args(NULL, os, 0, 0, pool));
+
+ SVN_ERR(svn_repos_db_logfiles(&logfiles,
+ opt_state->repository_path,
+ only_unused,
+ pool));
+
+ /* Loop, printing log files. We append the log paths to the
+ repository path, making sure to return everything to the native
+ style before printing. */
+ for (i = 0; i < logfiles->nelts; i++)
+ {
+ const char *log_utf8;
+ log_utf8 = svn_dirent_join(opt_state->repository_path,
+ APR_ARRAY_IDX(logfiles, i, const char *),
+ pool);
+ log_utf8 = svn_dirent_local_style(log_utf8, pool);
+ SVN_ERR(svn_cmdline_printf(pool, "%s\n", log_utf8));
+ }
+
+ return SVN_NO_ERROR;
+}
+
+
+/* This implements `svn_opt_subcommand_t'. */
+static svn_error_t *
+subcommand_list_dblogs(apr_getopt_t *os, void *baton, apr_pool_t *pool)
+{
+ SVN_ERR(list_dblogs(os, baton, FALSE, pool));
+ return SVN_NO_ERROR;
+}
+
+
+/* This implements `svn_opt_subcommand_t'. */
+static svn_error_t *
+subcommand_list_unused_dblogs(apr_getopt_t *os, void *baton, apr_pool_t *pool)
+{
+ /* Expect no more arguments. */
+ SVN_ERR(parse_args(NULL, os, 0, 0, pool));
+
+ SVN_ERR(list_dblogs(os, baton, TRUE, pool));
+ return SVN_NO_ERROR;
+}
+
+
+/* This implements `svn_opt_subcommand_t'. */
+static svn_error_t *
+subcommand_rmtxns(apr_getopt_t *os, void *baton, apr_pool_t *pool)
+{
+ struct svnadmin_opt_state *opt_state = baton;
+ svn_repos_t *repos;
+ svn_fs_t *fs;
+ svn_fs_txn_t *txn;
+ apr_array_header_t *args;
+ int i;
+ apr_pool_t *subpool = svn_pool_create(pool);
+
+ SVN_ERR(svn_opt_parse_all_args(&args, os, pool));
+
+ SVN_ERR(open_repos(&repos, opt_state->repository_path, pool));
+ fs = svn_repos_fs(repos);
+
+ /* All the rest of the arguments are transaction names. */
+ for (i = 0; i < args->nelts; i++)
+ {
+ const char *txn_name = APR_ARRAY_IDX(args, i, const char *);
+ const char *txn_name_utf8;
+ svn_error_t *err;
+
+ svn_pool_clear(subpool);
+
+ SVN_ERR(svn_utf_cstring_to_utf8(&txn_name_utf8, txn_name, subpool));
+
+ /* Try to open the txn. If that succeeds, try to abort it. */
+ err = svn_fs_open_txn(&txn, fs, txn_name_utf8, subpool);
+ if (! err)
+ err = svn_fs_abort_txn(txn, subpool);
+
+ /* If either the open or the abort of the txn fails because that
+ transaction is dead, just try to purge the thing. Else,
+ there was either an error worth reporting, or not error at
+ all. */
+ if (err && (err->apr_err == SVN_ERR_FS_TRANSACTION_DEAD))
+ {
+ svn_error_clear(err);
+ err = svn_fs_purge_txn(fs, txn_name_utf8, subpool);
+ }
+
+ /* If we had a real from the txn open, abort, or purge, we clear
+ that error and just report to the user that we had an issue
+ with this particular txn. */
+ if (err)
+ {
+ svn_handle_error2(err, stderr, FALSE /* non-fatal */, "svnadmin: ");
+ svn_error_clear(err);
+ }
+ else if (! opt_state->quiet)
+ {
+ SVN_ERR(svn_cmdline_printf(subpool, _("Transaction '%s' removed.\n"),
+ txn_name));
+ }
+ }
+
+ svn_pool_destroy(subpool);
+
+ return SVN_NO_ERROR;
+}
+
+
+/* A helper for the 'setrevprop' and 'setlog' commands. Expects
+ OPT_STATE->use_pre_revprop_change_hook and
+ OPT_STATE->use_post_revprop_change_hook to be set appropriately. */
+static svn_error_t *
+set_revprop(const char *prop_name, const char *filename,
+ struct svnadmin_opt_state *opt_state, apr_pool_t *pool)
+{
+ svn_repos_t *repos;
+ svn_string_t *prop_value = svn_string_create_empty(pool);
+ svn_stringbuf_t *file_contents;
+
+ SVN_ERR(svn_stringbuf_from_file2(&file_contents, filename, pool));
+
+ prop_value->data = file_contents->data;
+ prop_value->len = file_contents->len;
+
+ SVN_ERR(svn_subst_translate_string2(&prop_value, NULL, NULL, prop_value,
+ NULL, FALSE, pool, pool));
+
+ /* Open the filesystem */
+ SVN_ERR(open_repos(&repos, opt_state->repository_path, pool));
+
+ /* If we are bypassing the hooks system, we just hit the filesystem
+ directly. */
+ SVN_ERR(svn_repos_fs_change_rev_prop4(
+ repos, opt_state->start_revision.value.number,
+ NULL, prop_name, NULL, prop_value,
+ opt_state->use_pre_revprop_change_hook,
+ opt_state->use_post_revprop_change_hook,
+ NULL, NULL, pool));
+
+ return SVN_NO_ERROR;
+}
+
+
+/* This implements `svn_opt_subcommand_t'. */
+static svn_error_t *
+subcommand_setrevprop(apr_getopt_t *os, void *baton, apr_pool_t *pool)
+{
+ struct svnadmin_opt_state *opt_state = baton;
+ apr_array_header_t *args;
+ const char *prop_name, *filename;
+
+ /* Expect two more arguments: NAME FILE */
+ SVN_ERR(parse_args(&args, os, 2, 2, pool));
+ prop_name = APR_ARRAY_IDX(args, 0, const char *);
+ filename = APR_ARRAY_IDX(args, 1, const char *);
+ SVN_ERR(target_arg_to_dirent(&filename, filename, pool));
+
+ if (opt_state->start_revision.kind != svn_opt_revision_number)
+ return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
+ _("Missing revision"));
+ else if (opt_state->end_revision.kind != svn_opt_revision_unspecified)
+ return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
+ _("Only one revision allowed"));
+
+ return set_revprop(prop_name, filename, opt_state, pool);
+}
+
+
+/* This implements `svn_opt_subcommand_t'. */
+static svn_error_t *
+subcommand_setuuid(apr_getopt_t *os, void *baton, apr_pool_t *pool)
+{
+ struct svnadmin_opt_state *opt_state = baton;
+ apr_array_header_t *args;
+ svn_repos_t *repos;
+ svn_fs_t *fs;
+ const char *uuid = NULL;
+
+ /* Expect zero or one more arguments: [UUID] */
+ SVN_ERR(parse_args(&args, os, 0, 1, pool));
+ if (args->nelts == 1)
+ uuid = APR_ARRAY_IDX(args, 0, const char *);
+
+ SVN_ERR(open_repos(&repos, opt_state->repository_path, pool));
+ fs = svn_repos_fs(repos);
+ return svn_fs_set_uuid(fs, uuid, pool);
+}
+
+
+/* This implements `svn_opt_subcommand_t'. */
+static svn_error_t *
+subcommand_setlog(apr_getopt_t *os, void *baton, apr_pool_t *pool)
+{
+ struct svnadmin_opt_state *opt_state = baton;
+ apr_array_header_t *args;
+ const char *filename;
+
+ /* Expect one more argument: FILE */
+ SVN_ERR(parse_args(&args, os, 1, 1, pool));
+ filename = APR_ARRAY_IDX(args, 0, const char *);
+ SVN_ERR(target_arg_to_dirent(&filename, filename, pool));
+
+ if (opt_state->start_revision.kind != svn_opt_revision_number)
+ return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
+ _("Missing revision"));
+ else if (opt_state->end_revision.kind != svn_opt_revision_unspecified)
+ return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
+ _("Only one revision allowed"));
+
+ /* set_revprop() responds only to pre-/post-revprop-change opts. */
+ if (!opt_state->bypass_hooks)
+ {
+ opt_state->use_pre_revprop_change_hook = TRUE;
+ opt_state->use_post_revprop_change_hook = TRUE;
+ }
+
+ return set_revprop(SVN_PROP_REVISION_LOG, filename, opt_state, pool);
+}
+
+
+/* This implements 'svn_opt_subcommand_t'. */
+static svn_error_t *
+subcommand_pack(apr_getopt_t *os, void *baton, apr_pool_t *pool)
+{
+ struct svnadmin_opt_state *opt_state = baton;
+ svn_repos_t *repos;
+ svn_stream_t *progress_stream = NULL;
+
+ /* Expect no more arguments. */
+ SVN_ERR(parse_args(NULL, os, 0, 0, pool));
+
+ SVN_ERR(open_repos(&repos, opt_state->repository_path, pool));
+
+ /* Progress feedback goes to STDOUT, unless they asked to suppress it. */
+ if (! opt_state->quiet)
+ progress_stream = recode_stream_create(stdout, pool);
+
+ return svn_error_trace(
+ svn_repos_fs_pack2(repos, !opt_state->quiet ? repos_notify_handler : NULL,
+ progress_stream, check_cancel, NULL, pool));
+}
+
+
+/* This implements `svn_opt_subcommand_t'. */
+static svn_error_t *
+subcommand_verify(apr_getopt_t *os, void *baton, apr_pool_t *pool)
+{
+ struct svnadmin_opt_state *opt_state = baton;
+ svn_repos_t *repos;
+ svn_fs_t *fs;
+ svn_revnum_t youngest, lower, upper;
+ svn_stream_t *progress_stream = NULL;
+
+ /* Expect no more arguments. */
+ SVN_ERR(parse_args(NULL, os, 0, 0, pool));
+
+ if (opt_state->txn_id
+ && (opt_state->start_revision.kind != svn_opt_revision_unspecified
+ || opt_state->end_revision.kind != svn_opt_revision_unspecified))
+ {
+ return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
+ _("--revision (-r) and --transaction (-t) "
+ "are mutually exclusive"));
+ }
+
+ SVN_ERR(open_repos(&repos, opt_state->repository_path, pool));
+ fs = svn_repos_fs(repos);
+ SVN_ERR(svn_fs_youngest_rev(&youngest, fs, pool));
+
+ /* Usage 2. */
+ if (opt_state->txn_id)
+ {
+ svn_fs_txn_t *txn;
+ svn_fs_root_t *root;
+
+ SVN_ERR(svn_fs_open_txn(&txn, fs, opt_state->txn_id, pool));
+ SVN_ERR(svn_fs_txn_root(&root, txn, pool));
+ SVN_ERR(svn_fs_verify_root(root, pool));
+ return SVN_NO_ERROR;
+ }
+ else
+ /* Usage 1. */
+ ;
+
+ /* Find the revision numbers at which to start and end. */
+ SVN_ERR(get_revnum(&lower, &opt_state->start_revision,
+ youngest, repos, pool));
+ SVN_ERR(get_revnum(&upper, &opt_state->end_revision,
+ youngest, repos, pool));
+
+ if (upper == SVN_INVALID_REVNUM)
+ {
+ upper = lower;
+ }
+
+ if (! opt_state->quiet)
+ progress_stream = recode_stream_create(stderr, pool);
+
+ return svn_repos_verify_fs2(repos, lower, upper,
+ !opt_state->quiet
+ ? repos_notify_handler : NULL,
+ progress_stream, check_cancel, NULL, pool);
+}
+
+/* This implements `svn_opt_subcommand_t'. */
+svn_error_t *
+subcommand_hotcopy(apr_getopt_t *os, void *baton, apr_pool_t *pool)
+{
+ struct svnadmin_opt_state *opt_state = baton;
+ apr_array_header_t *targets;
+ const char *new_repos_path;
+
+ /* Expect one more argument: NEW_REPOS_PATH */
+ SVN_ERR(parse_args(&targets, os, 1, 1, pool));
+ new_repos_path = APR_ARRAY_IDX(targets, 0, const char *);
+ SVN_ERR(target_arg_to_dirent(&new_repos_path, new_repos_path, pool));
+
+ return svn_repos_hotcopy2(opt_state->repository_path, new_repos_path,
+ opt_state->clean_logs, opt_state->incremental,
+ check_cancel, NULL, pool);
+}
+
+/* This implements `svn_opt_subcommand_t'. */
+static svn_error_t *
+subcommand_lock(apr_getopt_t *os, void *baton, apr_pool_t *pool)
+{
+ struct svnadmin_opt_state *opt_state = baton;
+ svn_repos_t *repos;
+ svn_fs_t *fs;
+ svn_fs_access_t *access;
+ apr_array_header_t *args;
+ const char *username;
+ const char *lock_path;
+ const char *comment_file_name;
+ svn_stringbuf_t *file_contents;
+ const char *lock_path_utf8;
+ svn_lock_t *lock;
+ const char *lock_token = NULL;
+
+ /* Expect three more arguments: PATH USERNAME COMMENT-FILE */
+ SVN_ERR(parse_args(&args, os, 3, 4, pool));
+ lock_path = APR_ARRAY_IDX(args, 0, const char *);
+ username = APR_ARRAY_IDX(args, 1, const char *);
+ comment_file_name = APR_ARRAY_IDX(args, 2, const char *);
+
+ /* Expect one more optional argument: TOKEN */
+ if (args->nelts == 4)
+ lock_token = APR_ARRAY_IDX(args, 3, const char *);
+
+ SVN_ERR(target_arg_to_dirent(&comment_file_name, comment_file_name, pool));
+
+ SVN_ERR(open_repos(&repos, opt_state->repository_path, pool));
+ fs = svn_repos_fs(repos);
+
+ /* Create an access context describing the user. */
+ SVN_ERR(svn_fs_create_access(&access, username, pool));
+
+ /* Attach the access context to the filesystem. */
+ SVN_ERR(svn_fs_set_access(fs, access));
+
+ SVN_ERR(svn_stringbuf_from_file2(&file_contents, comment_file_name, pool));
+
+ SVN_ERR(svn_utf_cstring_to_utf8(&lock_path_utf8, lock_path, pool));
+
+ if (opt_state->bypass_hooks)
+ SVN_ERR(svn_fs_lock(&lock, fs, lock_path_utf8,
+ lock_token,
+ file_contents->data, /* comment */
+ 0, /* is_dav_comment */
+ 0, /* no expiration time. */
+ SVN_INVALID_REVNUM,
+ FALSE, pool));
+ else
+ SVN_ERR(svn_repos_fs_lock(&lock, repos, lock_path_utf8,
+ lock_token,
+ file_contents->data, /* comment */
+ 0, /* is_dav_comment */
+ 0, /* no expiration time. */
+ SVN_INVALID_REVNUM,
+ FALSE, pool));
+
+ SVN_ERR(svn_cmdline_printf(pool, _("'%s' locked by user '%s'.\n"),
+ lock_path, username));
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+subcommand_lslocks(apr_getopt_t *os, void *baton, apr_pool_t *pool)
+{
+ struct svnadmin_opt_state *opt_state = baton;
+ apr_array_header_t *targets;
+ svn_repos_t *repos;
+ const char *fs_path = "/";
+ apr_hash_t *locks;
+ apr_hash_index_t *hi;
+
+ SVN_ERR(svn_opt__args_to_target_array(&targets, os,
+ apr_array_make(pool, 0,
+ sizeof(const char *)),
+ pool));
+ if (targets->nelts > 1)
+ return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, 0,
+ _("Too many arguments given"));
+ if (targets->nelts)
+ fs_path = APR_ARRAY_IDX(targets, 0, const char *);
+
+ SVN_ERR(open_repos(&repos, opt_state->repository_path, pool));
+
+ /* Fetch all locks on or below the root directory. */
+ SVN_ERR(svn_repos_fs_get_locks2(&locks, repos, fs_path, svn_depth_infinity,
+ NULL, NULL, pool));
+
+ for (hi = apr_hash_first(pool, locks); hi; hi = apr_hash_next(hi))
+ {
+ const char *cr_date, *exp_date = "";
+ const char *path = svn__apr_hash_index_key(hi);
+ svn_lock_t *lock = svn__apr_hash_index_val(hi);
+ int comment_lines = 0;
+
+ cr_date = svn_time_to_human_cstring(lock->creation_date, pool);
+
+ if (lock->expiration_date)
+ exp_date = svn_time_to_human_cstring(lock->expiration_date, pool);
+
+ if (lock->comment)
+ comment_lines = svn_cstring_count_newlines(lock->comment) + 1;
+
+ SVN_ERR(svn_cmdline_printf(pool, _("Path: %s\n"), path));
+ SVN_ERR(svn_cmdline_printf(pool, _("UUID Token: %s\n"), lock->token));
+ SVN_ERR(svn_cmdline_printf(pool, _("Owner: %s\n"), lock->owner));
+ SVN_ERR(svn_cmdline_printf(pool, _("Created: %s\n"), cr_date));
+ SVN_ERR(svn_cmdline_printf(pool, _("Expires: %s\n"), exp_date));
+ SVN_ERR(svn_cmdline_printf(pool,
+ Q_("Comment (%i line):\n%s\n\n",
+ "Comment (%i lines):\n%s\n\n",
+ comment_lines),
+ comment_lines,
+ lock->comment ? lock->comment : ""));
+ }
+
+ return SVN_NO_ERROR;
+}
+
+
+
+static svn_error_t *
+subcommand_rmlocks(apr_getopt_t *os, void *baton, apr_pool_t *pool)
+{
+ struct svnadmin_opt_state *opt_state = baton;
+ svn_repos_t *repos;
+ svn_fs_t *fs;
+ svn_fs_access_t *access;
+ svn_error_t *err;
+ apr_array_header_t *args;
+ int i;
+ const char *username;
+ apr_pool_t *subpool = svn_pool_create(pool);
+
+ SVN_ERR(open_repos(&repos, opt_state->repository_path, pool));
+ fs = svn_repos_fs(repos);
+
+ /* svn_fs_unlock() demands that some username be associated with the
+ filesystem, so just use the UID of the person running 'svnadmin'.*/
+ username = svn_user_get_name(pool);
+ if (! username)
+ username = "administrator";
+
+ /* Create an access context describing the current user. */
+ SVN_ERR(svn_fs_create_access(&access, username, pool));
+
+ /* Attach the access context to the filesystem. */
+ SVN_ERR(svn_fs_set_access(fs, access));
+
+ /* Parse out any options. */
+ SVN_ERR(svn_opt_parse_all_args(&args, os, pool));
+
+ /* Our usage requires at least one FS path. */
+ if (args->nelts == 0)
+ return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, 0,
+ _("No paths to unlock provided"));
+
+ /* All the rest of the arguments are paths from which to remove locks. */
+ for (i = 0; i < args->nelts; i++)
+ {
+ const char *lock_path = APR_ARRAY_IDX(args, i, const char *);
+ const char *lock_path_utf8;
+ svn_lock_t *lock;
+
+ SVN_ERR(svn_utf_cstring_to_utf8(&lock_path_utf8, lock_path, subpool));
+
+ /* Fetch the path's svn_lock_t. */
+ err = svn_fs_get_lock(&lock, fs, lock_path_utf8, subpool);
+ if (err)
+ goto move_on;
+ if (! lock)
+ {
+ SVN_ERR(svn_cmdline_printf(subpool,
+ _("Path '%s' isn't locked.\n"),
+ lock_path));
+ continue;
+ }
+
+ /* Now forcibly destroy the lock. */
+ err = svn_fs_unlock(fs, lock_path_utf8,
+ lock->token, 1 /* force */, subpool);
+ if (err)
+ goto move_on;
+
+ SVN_ERR(svn_cmdline_printf(subpool,
+ _("Removed lock on '%s'.\n"), lock->path));
+
+ move_on:
+ if (err)
+ {
+ /* Print the error, but move on to the next lock. */
+ svn_handle_error2(err, stderr, FALSE /* non-fatal */, "svnadmin: ");
+ svn_error_clear(err);
+ }
+
+ svn_pool_clear(subpool);
+ }
+
+ svn_pool_destroy(subpool);
+ return SVN_NO_ERROR;
+}
+
+
+/* This implements `svn_opt_subcommand_t'. */
+static svn_error_t *
+subcommand_unlock(apr_getopt_t *os, void *baton, apr_pool_t *pool)
+{
+ struct svnadmin_opt_state *opt_state = baton;
+ svn_repos_t *repos;
+ svn_fs_t *fs;
+ svn_fs_access_t *access;
+ apr_array_header_t *args;
+ const char *username;
+ const char *lock_path;
+ const char *lock_path_utf8;
+ const char *lock_token = NULL;
+
+ /* Expect three more arguments: PATH USERNAME TOKEN */
+ SVN_ERR(parse_args(&args, os, 3, 3, pool));
+ lock_path = APR_ARRAY_IDX(args, 0, const char *);
+ username = APR_ARRAY_IDX(args, 1, const char *);
+ lock_token = APR_ARRAY_IDX(args, 2, const char *);
+
+ /* Open the repos/FS, and associate an access context containing
+ USERNAME. */
+ SVN_ERR(open_repos(&repos, opt_state->repository_path, pool));
+ fs = svn_repos_fs(repos);
+ SVN_ERR(svn_fs_create_access(&access, username, pool));
+ SVN_ERR(svn_fs_set_access(fs, access));
+
+ SVN_ERR(svn_utf_cstring_to_utf8(&lock_path_utf8, lock_path, pool));
+ if (opt_state->bypass_hooks)
+ SVN_ERR(svn_fs_unlock(fs, lock_path_utf8, lock_token,
+ FALSE, pool));
+ else
+ SVN_ERR(svn_repos_fs_unlock(repos, lock_path_utf8, lock_token,
+ FALSE, pool));
+
+ SVN_ERR(svn_cmdline_printf(pool, _("'%s' unlocked by user '%s'.\n"),
+ lock_path, username));
+ return SVN_NO_ERROR;
+}
+
+
+/* This implements `svn_opt_subcommand_t'. */
+static svn_error_t *
+subcommand_upgrade(apr_getopt_t *os, void *baton, apr_pool_t *pool)
+{
+ svn_error_t *err;
+ struct svnadmin_opt_state *opt_state = baton;
+ svn_stream_t *stdout_stream;
+
+ /* Expect no more arguments. */
+ SVN_ERR(parse_args(NULL, os, 0, 0, pool));
+
+ SVN_ERR(svn_stream_for_stdout(&stdout_stream, pool));
+
+ /* Restore default signal handlers. */
+ setup_cancellation_signals(SIG_DFL);
+
+ err = svn_repos_upgrade2(opt_state->repository_path, TRUE,
+ repos_notify_handler, stdout_stream, pool);
+ if (err)
+ {
+ if (APR_STATUS_IS_EAGAIN(err->apr_err))
+ {
+ svn_error_clear(err);
+ err = SVN_NO_ERROR;
+ if (! opt_state->wait)
+ return svn_error_create(SVN_ERR_REPOS_LOCKED, NULL,
+ _("Failed to get exclusive repository "
+ "access; perhaps another process\n"
+ "such as httpd, svnserve or svn "
+ "has it open?"));
+ SVN_ERR(svn_cmdline_printf(pool,
+ _("Waiting on repository lock; perhaps"
+ " another process has it open?\n")));
+ SVN_ERR(svn_cmdline_fflush(stdout));
+ SVN_ERR(svn_repos_upgrade2(opt_state->repository_path, FALSE,
+ repos_notify_handler, stdout_stream,
+ pool));
+ }
+ else if (err->apr_err == SVN_ERR_FS_UNSUPPORTED_UPGRADE)
+ {
+ return svn_error_quick_wrap(err,
+ _("Upgrade of this repository's underlying versioned "
+ "filesystem is not supported; consider "
+ "dumping and loading the data elsewhere"));
+ }
+ else if (err->apr_err == SVN_ERR_REPOS_UNSUPPORTED_UPGRADE)
+ {
+ return svn_error_quick_wrap(err,
+ _("Upgrade of this repository is not supported; consider "
+ "dumping and loading the data elsewhere"));
+ }
+ }
+ SVN_ERR(err);
+
+ SVN_ERR(svn_cmdline_printf(pool, _("\nUpgrade completed.\n")));
+ return SVN_NO_ERROR;
+}
+
+
+
+/** Main. **/
+
+/* Report and clear the error ERR, and return EXIT_FAILURE. */
+#define EXIT_ERROR(err) \
+ svn_cmdline_handle_exit_error(err, NULL, "svnadmin: ")
+
+/* A redefinition of the public SVN_INT_ERR macro, that suppresses the
+ * error message if it is SVN_ERR_IO_PIPE_WRITE_ERROR, amd with the
+ * program name 'svnadmin' instead of 'svn'. */
+#undef SVN_INT_ERR
+#define SVN_INT_ERR(expr) \
+ do { \
+ svn_error_t *svn_err__temp = (expr); \
+ if (svn_err__temp) \
+ return EXIT_ERROR(svn_err__temp); \
+ } while (0)
+
+static int
+sub_main(int argc, const char *argv[], apr_pool_t *pool)
+{
+ svn_error_t *err;
+ apr_status_t apr_err;
+
+ const svn_opt_subcommand_desc2_t *subcommand = NULL;
+ struct svnadmin_opt_state opt_state = { 0 };
+ apr_getopt_t *os;
+ int opt_id;
+ apr_array_header_t *received_opts;
+ int i;
+ svn_boolean_t dash_F_arg = FALSE;
+
+ received_opts = apr_array_make(pool, SVN_OPT_MAX_OPTIONS, sizeof(int));
+
+ /* Check library versions */
+ SVN_INT_ERR(check_lib_versions());
+
+ /* Initialize the FS library. */
+ SVN_INT_ERR(svn_fs_initialize(pool));
+
+ if (argc <= 1)
+ {
+ SVN_INT_ERR(subcommand_help(NULL, NULL, pool));
+ return EXIT_FAILURE;
+ }
+
+ /* Initialize opt_state. */
+ opt_state.start_revision.kind = svn_opt_revision_unspecified;
+ opt_state.end_revision.kind = svn_opt_revision_unspecified;
+ opt_state.memory_cache_size = svn_cache_config_get()->cache_size;
+
+ /* Parse options. */
+ SVN_INT_ERR(svn_cmdline__getopt_init(&os, argc, argv, pool));
+
+ os->interleave = 1;
+
+ while (1)
+ {
+ const char *opt_arg;
+ const char *utf8_opt_arg;
+
+ /* Parse the next option. */
+ apr_err = apr_getopt_long(os, options_table, &opt_id, &opt_arg);
+ if (APR_STATUS_IS_EOF(apr_err))
+ break;
+ else if (apr_err)
+ {
+ SVN_INT_ERR(subcommand_help(NULL, NULL, pool));
+ return EXIT_FAILURE;
+ }
+
+ /* Stash the option code in an array before parsing it. */
+ APR_ARRAY_PUSH(received_opts, int) = opt_id;
+
+ switch (opt_id) {
+ case 'r':
+ {
+ if (opt_state.start_revision.kind != svn_opt_revision_unspecified)
+ {
+ err = svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
+ _("Multiple revision arguments encountered; "
+ "try '-r N:M' instead of '-r N -r M'"));
+ return EXIT_ERROR(err);
+ }
+ if (svn_opt_parse_revision(&(opt_state.start_revision),
+ &(opt_state.end_revision),
+ opt_arg, pool) != 0)
+ {
+ err = svn_utf_cstring_to_utf8(&utf8_opt_arg, opt_arg,
+ pool);
+
+ if (! err)
+ err = svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
+ _("Syntax error in revision argument '%s'"),
+ utf8_opt_arg);
+ return EXIT_ERROR(err);
+ }
+ }
+ break;
+ case 't':
+ opt_state.txn_id = opt_arg;
+ break;
+
+ case 'q':
+ opt_state.quiet = TRUE;
+ break;
+ case 'h':
+ case '?':
+ opt_state.help = TRUE;
+ break;
+ case 'M':
+ opt_state.memory_cache_size
+ = 0x100000 * apr_strtoi64(opt_arg, NULL, 0);
+ break;
+ case 'F':
+ SVN_INT_ERR(svn_utf_cstring_to_utf8(&utf8_opt_arg, opt_arg, pool));
+ SVN_INT_ERR(svn_stringbuf_from_file2(&(opt_state.filedata),
+ utf8_opt_arg, pool));
+ dash_F_arg = TRUE;
+ case svnadmin__version:
+ opt_state.version = TRUE;
+ break;
+ case svnadmin__incremental:
+ opt_state.incremental = TRUE;
+ break;
+ case svnadmin__deltas:
+ opt_state.use_deltas = TRUE;
+ break;
+ case svnadmin__ignore_uuid:
+ opt_state.uuid_action = svn_repos_load_uuid_ignore;
+ break;
+ case svnadmin__force_uuid:
+ opt_state.uuid_action = svn_repos_load_uuid_force;
+ break;
+ case svnadmin__pre_1_4_compatible:
+ opt_state.pre_1_4_compatible = TRUE;
+ break;
+ case svnadmin__pre_1_5_compatible:
+ opt_state.pre_1_5_compatible = TRUE;
+ break;
+ case svnadmin__pre_1_6_compatible:
+ opt_state.pre_1_6_compatible = TRUE;
+ break;
+ case svnadmin__compatible_version:
+ {
+ svn_version_t latest = { SVN_VER_MAJOR, SVN_VER_MINOR,
+ SVN_VER_PATCH, NULL };
+ svn_version_t *compatible_version;
+
+ /* Parse the version string which carries our target
+ compatibility. */
+ SVN_INT_ERR(svn_version__parse_version_string(&compatible_version,
+ opt_arg, pool));
+
+ /* We can't create repository with a version older than 1.0.0. */
+ if (! svn_version__at_least(compatible_version, 1, 0, 0))
+ {
+ err = svn_error_createf(SVN_ERR_UNSUPPORTED_FEATURE, NULL,
+ _("Cannot create pre-1.0-compatible "
+ "repositories"));
+ return EXIT_ERROR(err);
+ }
+
+ /* We can't create repository with a version newer than what
+ the running version of Subversion supports. */
+ if (! svn_version__at_least(&latest,
+ compatible_version->major,
+ compatible_version->minor,
+ compatible_version->patch))
+ {
+ err = svn_error_createf(SVN_ERR_UNSUPPORTED_FEATURE, NULL,
+ _("Cannot guarantee compatibility "
+ "beyond the current running version "
+ "(%s)"),
+ SVN_VER_NUM );
+ return EXIT_ERROR(err);
+ }
+
+ opt_state.compatible_version = compatible_version;
+ }
+ break;
+ case svnadmin__fs_type:
+ SVN_INT_ERR(svn_utf_cstring_to_utf8(&opt_state.fs_type, opt_arg, pool));
+ break;
+ case svnadmin__parent_dir:
+ SVN_INT_ERR(svn_utf_cstring_to_utf8(&opt_state.parent_dir, opt_arg,
+ pool));
+ opt_state.parent_dir
+ = svn_dirent_internal_style(opt_state.parent_dir, pool);
+ break;
+ case svnadmin__use_pre_commit_hook:
+ opt_state.use_pre_commit_hook = TRUE;
+ break;
+ case svnadmin__use_post_commit_hook:
+ opt_state.use_post_commit_hook = TRUE;
+ break;
+ case svnadmin__use_pre_revprop_change_hook:
+ opt_state.use_pre_revprop_change_hook = TRUE;
+ break;
+ case svnadmin__use_post_revprop_change_hook:
+ opt_state.use_post_revprop_change_hook = TRUE;
+ break;
+ case svnadmin__bdb_txn_nosync:
+ opt_state.bdb_txn_nosync = TRUE;
+ break;
+ case svnadmin__bdb_log_keep:
+ opt_state.bdb_log_keep = TRUE;
+ break;
+ case svnadmin__bypass_hooks:
+ opt_state.bypass_hooks = TRUE;
+ break;
+ case svnadmin__bypass_prop_validation:
+ opt_state.bypass_prop_validation = TRUE;
+ break;
+ case svnadmin__clean_logs:
+ opt_state.clean_logs = TRUE;
+ break;
+ case svnadmin__config_dir:
+ SVN_INT_ERR(svn_utf_cstring_to_utf8(&utf8_opt_arg, opt_arg, pool));
+ opt_state.config_dir =
+ apr_pstrdup(pool, svn_dirent_canonicalize(utf8_opt_arg, pool));
+ break;
+ case svnadmin__wait:
+ opt_state.wait = TRUE;
+ break;
+ default:
+ {
+ SVN_INT_ERR(subcommand_help(NULL, NULL, pool));
+ return EXIT_FAILURE;
+ }
+ } /* close `switch' */
+ } /* close `while' */
+
+ /* If the user asked for help, then the rest of the arguments are
+ the names of subcommands to get help on (if any), or else they're
+ just typos/mistakes. Whatever the case, the subcommand to
+ actually run is subcommand_help(). */
+ if (opt_state.help)
+ subcommand = svn_opt_get_canonical_subcommand2(cmd_table, "help");
+
+ /* If we're not running the `help' subcommand, then look for a
+ subcommand in the first argument. */
+ if (subcommand == NULL)
+ {
+ if (os->ind >= os->argc)
+ {
+ if (opt_state.version)
+ {
+ /* Use the "help" subcommand to handle the "--version" option. */
+ static const svn_opt_subcommand_desc2_t pseudo_cmd =
+ { "--version", subcommand_help, {0}, "",
+ {svnadmin__version, /* must accept its own option */
+ 'q', /* --quiet */
+ } };
+
+ subcommand = &pseudo_cmd;
+ }
+ else
+ {
+ svn_error_clear(svn_cmdline_fprintf(stderr, pool,
+ _("subcommand argument required\n")));
+ SVN_INT_ERR(subcommand_help(NULL, NULL, pool));
+ return EXIT_FAILURE;
+ }
+ }
+ else
+ {
+ const char *first_arg = os->argv[os->ind++];
+ subcommand = svn_opt_get_canonical_subcommand2(cmd_table, first_arg);
+ if (subcommand == NULL)
+ {
+ const char *first_arg_utf8;
+ SVN_INT_ERR(svn_utf_cstring_to_utf8(&first_arg_utf8,
+ first_arg, pool));
+ svn_error_clear(
+ svn_cmdline_fprintf(stderr, pool,
+ _("Unknown subcommand: '%s'\n"),
+ first_arg_utf8));
+ SVN_INT_ERR(subcommand_help(NULL, NULL, pool));
+ return EXIT_FAILURE;
+ }
+ }
+ }
+
+ /* Every subcommand except `help' and `freeze' with '-F' require a
+ second argument -- the repository path. Parse it out here and
+ store it in opt_state. */
+ if (!(subcommand->cmd_func == subcommand_help
+ || (subcommand->cmd_func == subcommand_freeze && dash_F_arg)))
+ {
+ const char *repos_path = NULL;
+
+ if (os->ind >= os->argc)
+ {
+ err = svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
+ _("Repository argument required"));
+ return EXIT_ERROR(err);
+ }
+
+ if ((err = svn_utf_cstring_to_utf8(&repos_path,
+ os->argv[os->ind++], pool)))
+ {
+ return EXIT_ERROR(err);
+ }
+
+ if (svn_path_is_url(repos_path))
+ {
+ err = svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
+ _("'%s' is a URL when it should be a "
+ "local path"), repos_path);
+ return EXIT_ERROR(err);
+ }
+
+ opt_state.repository_path = svn_dirent_internal_style(repos_path, pool);
+ }
+
+ /* Check that the subcommand wasn't passed any inappropriate options. */
+ for (i = 0; i < received_opts->nelts; i++)
+ {
+ opt_id = APR_ARRAY_IDX(received_opts, i, int);
+
+ /* All commands implicitly accept --help, so just skip over this
+ when we see it. Note that we don't want to include this option
+ in their "accepted options" list because it would be awfully
+ redundant to display it in every commands' help text. */
+ if (opt_id == 'h' || opt_id == '?')
+ continue;
+
+ if (! svn_opt_subcommand_takes_option3(subcommand, opt_id, NULL))
+ {
+ const char *optstr;
+ const apr_getopt_option_t *badopt =
+ svn_opt_get_option_from_code2(opt_id, options_table, subcommand,
+ pool);
+ svn_opt_format_option(&optstr, badopt, FALSE, pool);
+ if (subcommand->name[0] == '-')
+ SVN_INT_ERR(subcommand_help(NULL, NULL, pool));
+ else
+ svn_error_clear(svn_cmdline_fprintf(stderr, pool
+ , _("Subcommand '%s' doesn't accept option '%s'\n"
+ "Type 'svnadmin help %s' for usage.\n"),
+ subcommand->name, optstr, subcommand->name));
+ return EXIT_FAILURE;
+ }
+ }
+
+ /* Set up our cancellation support. */
+ setup_cancellation_signals(signal_handler);
+
+#ifdef SIGPIPE
+ /* Disable SIGPIPE generation for the platforms that have it. */
+ apr_signal(SIGPIPE, SIG_IGN);
+#endif
+
+#ifdef SIGXFSZ
+ /* Disable SIGXFSZ generation for the platforms that have it, otherwise
+ * working with large files when compiled against an APR that doesn't have
+ * large file support will crash the program, which is uncool. */
+ apr_signal(SIGXFSZ, SIG_IGN);
+#endif
+
+ /* Configure FSFS caches for maximum efficiency with svnadmin.
+ * Also, apply the respective command line parameters, if given. */
+ {
+ svn_cache_config_t settings = *svn_cache_config_get();
+
+ settings.cache_size = opt_state.memory_cache_size;
+ settings.single_threaded = TRUE;
+
+ svn_cache_config_set(&settings);
+ }
+
+ /* Run the subcommand. */
+ err = (*subcommand->cmd_func)(os, &opt_state, pool);
+ if (err)
+ {
+ /* For argument-related problems, suggest using the 'help'
+ subcommand. */
+ if (err->apr_err == SVN_ERR_CL_INSUFFICIENT_ARGS
+ || err->apr_err == SVN_ERR_CL_ARG_PARSING_ERROR)
+ {
+ err = svn_error_quick_wrap(err,
+ _("Try 'svnadmin help' for more info"));
+ }
+ return EXIT_ERROR(err);
+ }
+ else
+ {
+ /* Ensure that everything is written to stdout, so the user will
+ see any print errors. */
+ err = svn_cmdline_fflush(stdout);
+ if (err)
+ {
+ return EXIT_ERROR(err);
+ }
+ return EXIT_SUCCESS;
+ }
+}
+
+int
+main(int argc, const char *argv[])
+{
+ apr_pool_t *pool;
+ int exit_code;
+
+ /* Initialize the app. */
+ if (svn_cmdline_init("svnadmin", stderr) != EXIT_SUCCESS)
+ return EXIT_FAILURE;
+
+ /* Create our top-level pool. Use a separate mutexless allocator,
+ * given this application is single threaded.
+ */
+ pool = apr_allocator_owner_get(svn_pool_create_allocator(FALSE));
+
+ exit_code = sub_main(argc, argv, pool);
+
+ svn_pool_destroy(pool);
+ return exit_code;
+}