1251881Speter/*
2251881Speter * svnadmin.c: Subversion server administration tool main file.
3251881Speter *
4251881Speter * ====================================================================
5251881Speter *    Licensed to the Apache Software Foundation (ASF) under one
6251881Speter *    or more contributor license agreements.  See the NOTICE file
7251881Speter *    distributed with this work for additional information
8251881Speter *    regarding copyright ownership.  The ASF licenses this file
9251881Speter *    to you under the Apache License, Version 2.0 (the
10251881Speter *    "License"); you may not use this file except in compliance
11251881Speter *    with the License.  You may obtain a copy of the License at
12251881Speter *
13251881Speter *      http://www.apache.org/licenses/LICENSE-2.0
14251881Speter *
15251881Speter *    Unless required by applicable law or agreed to in writing,
16251881Speter *    software distributed under the License is distributed on an
17251881Speter *    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
18251881Speter *    KIND, either express or implied.  See the License for the
19251881Speter *    specific language governing permissions and limitations
20251881Speter *    under the License.
21251881Speter * ====================================================================
22251881Speter */
23251881Speter
24251881Speter
25251881Speter#include <apr_file_io.h>
26251881Speter#include <apr_signal.h>
27251881Speter
28251881Speter#include "svn_hash.h"
29251881Speter#include "svn_pools.h"
30251881Speter#include "svn_cmdline.h"
31251881Speter#include "svn_error.h"
32251881Speter#include "svn_opt.h"
33251881Speter#include "svn_utf.h"
34251881Speter#include "svn_subst.h"
35251881Speter#include "svn_dirent_uri.h"
36251881Speter#include "svn_path.h"
37251881Speter#include "svn_config.h"
38251881Speter#include "svn_repos.h"
39251881Speter#include "svn_cache_config.h"
40251881Speter#include "svn_version.h"
41251881Speter#include "svn_props.h"
42299742Sdim#include "svn_sorts.h"
43251881Speter#include "svn_time.h"
44251881Speter#include "svn_user.h"
45251881Speter#include "svn_xml.h"
46251881Speter
47299742Sdim#include "private/svn_cmdline_private.h"
48251881Speter#include "private/svn_opt_private.h"
49299742Sdim#include "private/svn_sorts_private.h"
50251881Speter#include "private/svn_subr_private.h"
51251881Speter
52251881Speter#include "svn_private_config.h"
53251881Speter
54251881Speter
55251881Speter/*** Code. ***/
56251881Speter
57299742Sdim/* FSFS format 7's "block-read" feature performs poorly with small caches.
58299742Sdim * Enable it only if caches above this threshold have been configured.
59299742Sdim * The current threshold is 64MB. */
60299742Sdim#define BLOCK_READ_CACHE_THRESHOLD (0x40 * 0x100000)
61299742Sdim
62251881Speter/* A flag to see if we've been cancelled by the client or not. */
63251881Speterstatic volatile sig_atomic_t cancelled = FALSE;
64251881Speter
65251881Speter/* A signal handler to support cancellation. */
66251881Speterstatic void
67251881Spetersignal_handler(int signum)
68251881Speter{
69251881Speter  apr_signal(signum, SIG_IGN);
70251881Speter  cancelled = TRUE;
71251881Speter}
72251881Speter
73251881Speter
74251881Speter/* A helper to set up the cancellation signal handlers. */
75251881Speterstatic void
76251881Spetersetup_cancellation_signals(void (*handler)(int signum))
77251881Speter{
78251881Speter  apr_signal(SIGINT, handler);
79251881Speter#ifdef SIGBREAK
80251881Speter  /* SIGBREAK is a Win32 specific signal generated by ctrl-break. */
81251881Speter  apr_signal(SIGBREAK, handler);
82251881Speter#endif
83251881Speter#ifdef SIGHUP
84251881Speter  apr_signal(SIGHUP, handler);
85251881Speter#endif
86251881Speter#ifdef SIGTERM
87251881Speter  apr_signal(SIGTERM, handler);
88251881Speter#endif
89251881Speter}
90251881Speter
91251881Speter
92251881Speter/* Our cancellation callback. */
93251881Speterstatic svn_error_t *
94251881Spetercheck_cancel(void *baton)
95251881Speter{
96251881Speter  if (cancelled)
97251881Speter    return svn_error_create(SVN_ERR_CANCELLED, NULL, _("Caught signal"));
98251881Speter  else
99251881Speter    return SVN_NO_ERROR;
100251881Speter}
101251881Speter
102251881Speter
103251881Speter/* Custom filesystem warning function. */
104251881Speterstatic void
105251881Speterwarning_func(void *baton,
106251881Speter             svn_error_t *err)
107251881Speter{
108251881Speter  if (! err)
109251881Speter    return;
110299742Sdim  svn_handle_warning2(stderr, err, "svnadmin: ");
111251881Speter}
112251881Speter
113251881Speter
114251881Speter/* Helper to open a repository and set a warning func (so we don't
115251881Speter * SEGFAULT when libsvn_fs's default handler gets run).  */
116251881Speterstatic svn_error_t *
117251881Speteropen_repos(svn_repos_t **repos,
118251881Speter           const char *path,
119251881Speter           apr_pool_t *pool)
120251881Speter{
121299742Sdim  /* Enable the "block-read" feature (where it applies)? */
122299742Sdim  svn_boolean_t use_block_read
123299742Sdim    = svn_cache_config_get()->cache_size > BLOCK_READ_CACHE_THRESHOLD;
124299742Sdim
125251881Speter  /* construct FS configuration parameters: enable caches for r/o data */
126251881Speter  apr_hash_t *fs_config = apr_hash_make(pool);
127251881Speter  svn_hash_sets(fs_config, SVN_FS_CONFIG_FSFS_CACHE_DELTAS, "1");
128251881Speter  svn_hash_sets(fs_config, SVN_FS_CONFIG_FSFS_CACHE_FULLTEXTS, "1");
129251881Speter  svn_hash_sets(fs_config, SVN_FS_CONFIG_FSFS_CACHE_REVPROPS, "2");
130251881Speter  svn_hash_sets(fs_config, SVN_FS_CONFIG_FSFS_CACHE_NS,
131251881Speter                           svn_uuid_generate(pool));
132299742Sdim  svn_hash_sets(fs_config, SVN_FS_CONFIG_FSFS_BLOCK_READ,
133299742Sdim                           use_block_read ? "1" : "0");
134251881Speter
135251881Speter  /* now, open the requested repository */
136299742Sdim  SVN_ERR(svn_repos_open3(repos, path, fs_config, pool, pool));
137251881Speter  svn_fs_set_warning_func(svn_repos_fs(*repos), warning_func, NULL);
138251881Speter  return SVN_NO_ERROR;
139251881Speter}
140251881Speter
141251881Speter
142251881Speter/* Version compatibility check */
143251881Speterstatic svn_error_t *
144251881Spetercheck_lib_versions(void)
145251881Speter{
146251881Speter  static const svn_version_checklist_t checklist[] =
147251881Speter    {
148251881Speter      { "svn_subr",  svn_subr_version },
149251881Speter      { "svn_repos", svn_repos_version },
150251881Speter      { "svn_fs",    svn_fs_version },
151251881Speter      { "svn_delta", svn_delta_version },
152251881Speter      { NULL, NULL }
153251881Speter    };
154251881Speter  SVN_VERSION_DEFINE(my_version);
155251881Speter
156262253Speter  return svn_ver_check_list2(&my_version, checklist, svn_ver_equal);
157251881Speter}
158251881Speter
159251881Speter
160251881Speter
161251881Speter/** Subcommands. **/
162251881Speter
163251881Speterstatic svn_opt_subcommand_t
164251881Speter  subcommand_crashtest,
165251881Speter  subcommand_create,
166299742Sdim  subcommand_delrevprop,
167251881Speter  subcommand_deltify,
168251881Speter  subcommand_dump,
169251881Speter  subcommand_freeze,
170251881Speter  subcommand_help,
171251881Speter  subcommand_hotcopy,
172299742Sdim  subcommand_info,
173251881Speter  subcommand_load,
174251881Speter  subcommand_list_dblogs,
175251881Speter  subcommand_list_unused_dblogs,
176251881Speter  subcommand_lock,
177251881Speter  subcommand_lslocks,
178251881Speter  subcommand_lstxns,
179251881Speter  subcommand_pack,
180251881Speter  subcommand_recover,
181251881Speter  subcommand_rmlocks,
182251881Speter  subcommand_rmtxns,
183251881Speter  subcommand_setlog,
184251881Speter  subcommand_setrevprop,
185251881Speter  subcommand_setuuid,
186251881Speter  subcommand_unlock,
187251881Speter  subcommand_upgrade,
188251881Speter  subcommand_verify;
189251881Speter
190251881Speterenum svnadmin__cmdline_options_t
191251881Speter  {
192251881Speter    svnadmin__version = SVN_OPT_FIRST_LONGOPT_ID,
193251881Speter    svnadmin__incremental,
194299742Sdim    svnadmin__keep_going,
195251881Speter    svnadmin__deltas,
196251881Speter    svnadmin__ignore_uuid,
197251881Speter    svnadmin__force_uuid,
198251881Speter    svnadmin__fs_type,
199251881Speter    svnadmin__parent_dir,
200251881Speter    svnadmin__bdb_txn_nosync,
201251881Speter    svnadmin__bdb_log_keep,
202251881Speter    svnadmin__config_dir,
203251881Speter    svnadmin__bypass_hooks,
204251881Speter    svnadmin__bypass_prop_validation,
205299742Sdim    svnadmin__ignore_dates,
206251881Speter    svnadmin__use_pre_commit_hook,
207251881Speter    svnadmin__use_post_commit_hook,
208251881Speter    svnadmin__use_pre_revprop_change_hook,
209251881Speter    svnadmin__use_post_revprop_change_hook,
210251881Speter    svnadmin__clean_logs,
211251881Speter    svnadmin__wait,
212251881Speter    svnadmin__pre_1_4_compatible,
213251881Speter    svnadmin__pre_1_5_compatible,
214251881Speter    svnadmin__pre_1_6_compatible,
215299742Sdim    svnadmin__compatible_version,
216299742Sdim    svnadmin__check_normalization,
217299742Sdim    svnadmin__metadata_only
218251881Speter  };
219251881Speter
220251881Speter/* Option codes and descriptions.
221251881Speter *
222251881Speter * The entire list must be terminated with an entry of nulls.
223251881Speter */
224251881Speterstatic const apr_getopt_option_t options_table[] =
225251881Speter  {
226251881Speter    {"help",          'h', 0,
227251881Speter     N_("show help on a subcommand")},
228251881Speter
229251881Speter    {NULL,            '?', 0,
230251881Speter     N_("show help on a subcommand")},
231251881Speter
232251881Speter    {"version",       svnadmin__version, 0,
233251881Speter     N_("show program version information")},
234251881Speter
235251881Speter    {"revision",      'r', 1,
236251881Speter     N_("specify revision number ARG (or X:Y range)")},
237251881Speter
238251881Speter    {"transaction",       't', 1,
239251881Speter     N_("specify transaction name ARG")},
240251881Speter
241251881Speter    {"incremental",   svnadmin__incremental, 0,
242251881Speter     N_("dump or hotcopy incrementally")},
243251881Speter
244251881Speter    {"deltas",        svnadmin__deltas, 0,
245251881Speter     N_("use deltas in dump output")},
246251881Speter
247251881Speter    {"bypass-hooks",  svnadmin__bypass_hooks, 0,
248251881Speter     N_("bypass the repository hook system")},
249251881Speter
250251881Speter    {"bypass-prop-validation",  svnadmin__bypass_prop_validation, 0,
251251881Speter     N_("bypass property validation logic")},
252251881Speter
253299742Sdim    {"ignore-dates",  svnadmin__ignore_dates, 0,
254299742Sdim     N_("ignore revision datestamps found in the stream")},
255299742Sdim
256251881Speter    {"quiet",         'q', 0,
257299742Sdim     N_("no progress (only errors to stderr)")},
258251881Speter
259251881Speter    {"ignore-uuid",   svnadmin__ignore_uuid, 0,
260251881Speter     N_("ignore any repos UUID found in the stream")},
261251881Speter
262251881Speter    {"force-uuid",    svnadmin__force_uuid, 0,
263251881Speter     N_("set repos UUID to that found in stream, if any")},
264251881Speter
265251881Speter    {"fs-type",       svnadmin__fs_type, 1,
266299742Sdim     N_("type of repository:\n"
267299742Sdim        "                             'fsfs' (default), 'bdb' or 'fsx'\n"
268299742Sdim        "                             CAUTION: FSX is for EXPERIMENTAL use only!")},
269251881Speter
270251881Speter    {"parent-dir",    svnadmin__parent_dir, 1,
271251881Speter     N_("load at specified directory in repository")},
272251881Speter
273251881Speter    {"bdb-txn-nosync", svnadmin__bdb_txn_nosync, 0,
274251881Speter     N_("disable fsync at transaction commit [Berkeley DB]")},
275251881Speter
276251881Speter    {"bdb-log-keep",  svnadmin__bdb_log_keep, 0,
277251881Speter     N_("disable automatic log file removal [Berkeley DB]")},
278251881Speter
279251881Speter    {"config-dir",    svnadmin__config_dir, 1,
280251881Speter     N_("read user configuration files from directory ARG")},
281251881Speter
282251881Speter    {"clean-logs",    svnadmin__clean_logs, 0,
283251881Speter     N_("remove redundant Berkeley DB log files\n"
284251881Speter        "                             from source repository [Berkeley DB]")},
285251881Speter
286251881Speter    {"use-pre-commit-hook", svnadmin__use_pre_commit_hook, 0,
287251881Speter     N_("call pre-commit hook before committing revisions")},
288251881Speter
289251881Speter    {"use-post-commit-hook", svnadmin__use_post_commit_hook, 0,
290251881Speter     N_("call post-commit hook after committing revisions")},
291251881Speter
292251881Speter    {"use-pre-revprop-change-hook", svnadmin__use_pre_revprop_change_hook, 0,
293251881Speter     N_("call hook before changing revision property")},
294251881Speter
295251881Speter    {"use-post-revprop-change-hook", svnadmin__use_post_revprop_change_hook, 0,
296251881Speter     N_("call hook after changing revision property")},
297251881Speter
298251881Speter    {"wait",          svnadmin__wait, 0,
299251881Speter     N_("wait instead of exit if the repository is in\n"
300251881Speter        "                             use by another process")},
301251881Speter
302251881Speter    {"pre-1.4-compatible",     svnadmin__pre_1_4_compatible, 0,
303251881Speter     N_("deprecated; see --compatible-version")},
304251881Speter
305251881Speter    {"pre-1.5-compatible",     svnadmin__pre_1_5_compatible, 0,
306251881Speter     N_("deprecated; see --compatible-version")},
307251881Speter
308251881Speter    {"pre-1.6-compatible",     svnadmin__pre_1_6_compatible, 0,
309251881Speter     N_("deprecated; see --compatible-version")},
310251881Speter
311299742Sdim    {"keep-going",    svnadmin__keep_going, 0,
312299742Sdim     N_("continue verification after detecting a corruption")},
313299742Sdim
314251881Speter    {"memory-cache-size",     'M', 1,
315251881Speter     N_("size of the extra in-memory cache in MB used to\n"
316251881Speter        "                             minimize redundant operations. Default: 16.\n"
317251881Speter        "                             [used for FSFS repositories only]")},
318251881Speter
319251881Speter    {"compatible-version",     svnadmin__compatible_version, 1,
320251881Speter     N_("use repository format compatible with Subversion\n"
321251881Speter        "                             version ARG (\"1.5.5\", \"1.7\", etc.)")},
322251881Speter
323251881Speter    {"file", 'F', 1, N_("read repository paths from file ARG")},
324251881Speter
325299742Sdim    {"check-normalization", svnadmin__check_normalization, 0,
326299742Sdim     N_("report any names within the same directory or\n"
327299742Sdim        "                             svn:mergeinfo property value that differ only\n"
328299742Sdim        "                             in character representation, but are otherwise\n"
329299742Sdim        "                             identical")},
330299742Sdim
331299742Sdim    {"metadata-only", svnadmin__metadata_only, 0,
332299742Sdim     N_("verify metadata only (ignored for BDB),\n"
333299742Sdim        "                             checking against external corruption in\n"
334299742Sdim        "                             Subversion 1.9+ format repositories.\n")},
335299742Sdim
336251881Speter    {NULL}
337251881Speter  };
338251881Speter
339251881Speter
340251881Speter/* Array of available subcommands.
341251881Speter * The entire list must be terminated with an entry of nulls.
342251881Speter */
343251881Speterstatic const svn_opt_subcommand_desc2_t cmd_table[] =
344251881Speter{
345251881Speter  {"crashtest", subcommand_crashtest, {0}, N_
346251881Speter   ("usage: svnadmin crashtest REPOS_PATH\n\n"
347251881Speter    "Open the repository at REPOS_PATH, then abort, thus simulating\n"
348251881Speter    "a process that crashes while holding an open repository handle.\n"),
349251881Speter   {0} },
350251881Speter
351251881Speter  {"create", subcommand_create, {0}, N_
352251881Speter   ("usage: svnadmin create REPOS_PATH\n\n"
353251881Speter    "Create a new, empty repository at REPOS_PATH.\n"),
354251881Speter   {svnadmin__bdb_txn_nosync, svnadmin__bdb_log_keep,
355251881Speter    svnadmin__config_dir, svnadmin__fs_type, svnadmin__compatible_version,
356251881Speter    svnadmin__pre_1_4_compatible, svnadmin__pre_1_5_compatible,
357251881Speter    svnadmin__pre_1_6_compatible
358251881Speter    } },
359251881Speter
360299742Sdim  {"delrevprop", subcommand_delrevprop, {0}, N_
361299742Sdim   ("usage: 1. svnadmin delrevprop REPOS_PATH -r REVISION NAME\n"
362299742Sdim    "                   2. svnadmin delrevprop REPO_PATH -t TXN NAME\n\n"
363299742Sdim    "1. Delete the property NAME on revision REVISION.\n\n"
364299742Sdim    "Use --use-pre-revprop-change-hook/--use-post-revprop-change-hook to\n"
365299742Sdim    "trigger the revision property-related hooks (for example, if you want\n"
366299742Sdim    "an email notification sent from your post-revprop-change hook).\n\n"
367299742Sdim    "NOTE: Revision properties are not versioned, so this command will\n"
368299742Sdim    "irreversibly destroy the previous value of the property.\n\n"
369299742Sdim    "2. Delete the property NAME on transaction TXN.\n"),
370299742Sdim   {'r', 't', svnadmin__use_pre_revprop_change_hook,
371299742Sdim    svnadmin__use_post_revprop_change_hook} },
372299742Sdim
373251881Speter  {"deltify", subcommand_deltify, {0}, N_
374251881Speter   ("usage: svnadmin deltify [-r LOWER[:UPPER]] REPOS_PATH\n\n"
375251881Speter    "Run over the requested revision range, performing predecessor delti-\n"
376251881Speter    "fication on the paths changed in those revisions.  Deltification in\n"
377251881Speter    "essence compresses the repository by only storing the differences or\n"
378251881Speter    "delta from the preceding revision.  If no revisions are specified,\n"
379251881Speter    "this will simply deltify the HEAD revision.\n"),
380251881Speter   {'r', 'q', 'M'} },
381251881Speter
382251881Speter  {"dump", subcommand_dump, {0}, N_
383251881Speter   ("usage: svnadmin dump REPOS_PATH [-r LOWER[:UPPER] [--incremental]]\n\n"
384251881Speter    "Dump the contents of filesystem to stdout in a 'dumpfile'\n"
385251881Speter    "portable format, sending feedback to stderr.  Dump revisions\n"
386251881Speter    "LOWER rev through UPPER rev.  If no revisions are given, dump all\n"
387251881Speter    "revision trees.  If only LOWER is given, dump that one revision tree.\n"
388251881Speter    "If --incremental is passed, the first revision dumped will describe\n"
389251881Speter    "only the paths changed in that revision; otherwise it will describe\n"
390251881Speter    "every path present in the repository as of that revision.  (In either\n"
391251881Speter    "case, the second and subsequent revisions, if any, describe only paths\n"
392251881Speter    "changed in those revisions.)\n"),
393251881Speter  {'r', svnadmin__incremental, svnadmin__deltas, 'q', 'M'} },
394251881Speter
395251881Speter  {"freeze", subcommand_freeze, {0}, N_
396251881Speter   ("usage: 1. svnadmin freeze REPOS_PATH PROGRAM [ARG...]\n"
397251881Speter    "               2. svnadmin freeze -F FILE PROGRAM [ARG...]\n\n"
398251881Speter    "1. Run PROGRAM passing ARGS while holding a write-lock on REPOS_PATH.\n"
399251881Speter    "\n"
400251881Speter    "2. Like 1 except all repositories listed in FILE are locked. The file\n"
401251881Speter    "   format is repository paths separated by newlines.  Repositories are\n"
402251881Speter    "   locked in the same order as they are listed in the file.\n"),
403251881Speter   {'F'} },
404251881Speter
405251881Speter  {"help", subcommand_help, {"?", "h"}, N_
406251881Speter   ("usage: svnadmin help [SUBCOMMAND...]\n\n"
407251881Speter    "Describe the usage of this program or its subcommands.\n"),
408251881Speter   {0} },
409251881Speter
410251881Speter  {"hotcopy", subcommand_hotcopy, {0}, N_
411251881Speter   ("usage: svnadmin hotcopy REPOS_PATH NEW_REPOS_PATH\n\n"
412251881Speter    "Make a hot copy of a repository.\n"
413251881Speter    "If --incremental is passed, data which already exists at the destination\n"
414251881Speter    "is not copied again.  Incremental mode is implemented for FSFS repositories.\n"),
415299742Sdim   {svnadmin__clean_logs, svnadmin__incremental, 'q'} },
416251881Speter
417299742Sdim  {"info", subcommand_info, {0}, N_
418299742Sdim   ("usage: svnadmin info REPOS_PATH\n\n"
419299742Sdim    "Print information about the repository at REPOS_PATH.\n"),
420299742Sdim   {0} },
421299742Sdim
422251881Speter  {"list-dblogs", subcommand_list_dblogs, {0}, N_
423251881Speter   ("usage: svnadmin list-dblogs REPOS_PATH\n\n"
424251881Speter    "List all Berkeley DB log files.\n\n"
425251881Speter    "WARNING: Modifying or deleting logfiles which are still in use\n"
426251881Speter    "will cause your repository to be corrupted.\n"),
427251881Speter   {0} },
428251881Speter
429251881Speter  {"list-unused-dblogs", subcommand_list_unused_dblogs, {0}, N_
430251881Speter   ("usage: svnadmin list-unused-dblogs REPOS_PATH\n\n"
431251881Speter    "List unused Berkeley DB log files.\n\n"),
432251881Speter   {0} },
433251881Speter
434251881Speter  {"load", subcommand_load, {0}, N_
435251881Speter   ("usage: svnadmin load REPOS_PATH\n\n"
436251881Speter    "Read a 'dumpfile'-formatted stream from stdin, committing\n"
437251881Speter    "new revisions into the repository's filesystem.  If the repository\n"
438251881Speter    "was previously empty, its UUID will, by default, be changed to the\n"
439251881Speter    "one specified in the stream.  Progress feedback is sent to stdout.\n"
440251881Speter    "If --revision is specified, limit the loaded revisions to only those\n"
441251881Speter    "in the dump stream whose revision numbers match the specified range.\n"),
442251881Speter   {'q', 'r', svnadmin__ignore_uuid, svnadmin__force_uuid,
443299742Sdim    svnadmin__ignore_dates,
444251881Speter    svnadmin__use_pre_commit_hook, svnadmin__use_post_commit_hook,
445251881Speter    svnadmin__parent_dir, svnadmin__bypass_prop_validation, 'M'} },
446251881Speter
447251881Speter  {"lock", subcommand_lock, {0}, N_
448251881Speter   ("usage: svnadmin lock REPOS_PATH PATH USERNAME COMMENT-FILE [TOKEN]\n\n"
449251881Speter    "Lock PATH by USERNAME setting comments from COMMENT-FILE.\n"
450251881Speter    "If provided, use TOKEN as lock token.  Use --bypass-hooks to avoid\n"
451251881Speter    "triggering the pre-lock and post-lock hook scripts.\n"),
452251881Speter  {svnadmin__bypass_hooks} },
453251881Speter
454251881Speter  {"lslocks", subcommand_lslocks, {0}, N_
455251881Speter   ("usage: svnadmin lslocks REPOS_PATH [PATH-IN-REPOS]\n\n"
456251881Speter    "Print descriptions of all locks on or under PATH-IN-REPOS (which,\n"
457251881Speter    "if not provided, is the root of the repository).\n"),
458251881Speter   {0} },
459251881Speter
460251881Speter  {"lstxns", subcommand_lstxns, {0}, N_
461251881Speter   ("usage: svnadmin lstxns REPOS_PATH\n\n"
462251881Speter    "Print the names of all uncommitted transactions.\n"),
463251881Speter   {0} },
464251881Speter
465251881Speter  {"pack", subcommand_pack, {0}, N_
466251881Speter   ("usage: svnadmin pack REPOS_PATH\n\n"
467251881Speter    "Possibly compact the repository into a more efficient storage model.\n"
468251881Speter    "This may not apply to all repositories, in which case, exit.\n"),
469299742Sdim   {'q', 'M'} },
470251881Speter
471251881Speter  {"recover", subcommand_recover, {0}, N_
472251881Speter   ("usage: svnadmin recover REPOS_PATH\n\n"
473251881Speter    "Run the recovery procedure on a repository.  Do this if you've\n"
474251881Speter    "been getting errors indicating that recovery ought to be run.\n"
475251881Speter    "Berkeley DB recovery requires exclusive access and will\n"
476251881Speter    "exit if the repository is in use by another process.\n"),
477251881Speter   {svnadmin__wait} },
478251881Speter
479251881Speter  {"rmlocks", subcommand_rmlocks, {0}, N_
480251881Speter   ("usage: svnadmin rmlocks REPOS_PATH LOCKED_PATH...\n\n"
481251881Speter    "Unconditionally remove lock from each LOCKED_PATH.\n"),
482251881Speter   {0} },
483251881Speter
484251881Speter  {"rmtxns", subcommand_rmtxns, {0}, N_
485251881Speter   ("usage: svnadmin rmtxns REPOS_PATH TXN_NAME...\n\n"
486251881Speter    "Delete the named transaction(s).\n"),
487251881Speter   {'q'} },
488251881Speter
489251881Speter  {"setlog", subcommand_setlog, {0}, N_
490251881Speter   ("usage: svnadmin setlog REPOS_PATH -r REVISION FILE\n\n"
491251881Speter    "Set the log-message on revision REVISION to the contents of FILE.  Use\n"
492251881Speter    "--bypass-hooks to avoid triggering the revision-property-related hooks\n"
493251881Speter    "(for example, if you do not want an email notification sent\n"
494251881Speter    "from your post-revprop-change hook, or because the modification of\n"
495251881Speter    "revision properties has not been enabled in the pre-revprop-change\n"
496251881Speter    "hook).\n\n"
497251881Speter    "NOTE: Revision properties are not versioned, so this command will\n"
498251881Speter    "overwrite the previous log message.\n"),
499251881Speter   {'r', svnadmin__bypass_hooks} },
500251881Speter
501251881Speter  {"setrevprop", subcommand_setrevprop, {0}, N_
502299742Sdim   ("usage: 1. svnadmin setrevprop REPOS_PATH -r REVISION NAME FILE\n"
503299742Sdim    "                   2. svnadmin setrevprop REPOS_PATH -t TXN NAME FILE\n\n"
504299742Sdim    "1. Set the property NAME on revision REVISION to the contents of FILE.\n\n"
505299742Sdim    "Use --use-pre-revprop-change-hook/--use-post-revprop-change-hook to\n"
506299742Sdim    "trigger the revision property-related hooks (for example, if you want\n"
507299742Sdim    "an email notification sent from your post-revprop-change hook).\n\n"
508251881Speter    "NOTE: Revision properties are not versioned, so this command will\n"
509299742Sdim    "overwrite the previous value of the property.\n\n"
510299742Sdim    "2. Set the property NAME on transaction TXN to the contents of FILE.\n"),
511299742Sdim   {'r', 't', svnadmin__use_pre_revprop_change_hook,
512251881Speter    svnadmin__use_post_revprop_change_hook} },
513251881Speter
514251881Speter  {"setuuid", subcommand_setuuid, {0}, N_
515251881Speter   ("usage: svnadmin setuuid REPOS_PATH [NEW_UUID]\n\n"
516251881Speter    "Reset the repository UUID for the repository located at REPOS_PATH.  If\n"
517251881Speter    "NEW_UUID is provided, use that as the new repository UUID; otherwise,\n"
518251881Speter    "generate a brand new UUID for the repository.\n"),
519251881Speter   {0} },
520251881Speter
521251881Speter  {"unlock", subcommand_unlock, {0}, N_
522251881Speter   ("usage: svnadmin unlock REPOS_PATH LOCKED_PATH USERNAME TOKEN\n\n"
523251881Speter    "Unlock LOCKED_PATH (as USERNAME) after verifying that the token\n"
524251881Speter    "associated with the lock matches TOKEN.  Use --bypass-hooks to avoid\n"
525251881Speter    "triggering the pre-unlock and post-unlock hook scripts.\n"),
526251881Speter   {svnadmin__bypass_hooks} },
527251881Speter
528251881Speter  {"upgrade", subcommand_upgrade, {0}, N_
529251881Speter   ("usage: svnadmin upgrade REPOS_PATH\n\n"
530251881Speter    "Upgrade the repository located at REPOS_PATH to the latest supported\n"
531251881Speter    "schema version.\n\n"
532251881Speter    "This functionality is provided as a convenience for repository\n"
533251881Speter    "administrators who wish to make use of new Subversion functionality\n"
534251881Speter    "without having to undertake a potentially costly full repository dump\n"
535251881Speter    "and load operation.  As such, the upgrade performs only the minimum\n"
536251881Speter    "amount of work needed to accomplish this while still maintaining the\n"
537251881Speter    "integrity of the repository.  It does not guarantee the most optimized\n"
538251881Speter    "repository state as a dump and subsequent load would.\n"),
539251881Speter   {0} },
540251881Speter
541251881Speter  {"verify", subcommand_verify, {0}, N_
542251881Speter   ("usage: svnadmin verify REPOS_PATH\n\n"
543251881Speter    "Verify the data stored in the repository.\n"),
544299742Sdim   {'t', 'r', 'q', svnadmin__keep_going, 'M',
545299742Sdim    svnadmin__check_normalization, svnadmin__metadata_only} },
546251881Speter
547251881Speter  { NULL, NULL, {0}, NULL, {0} }
548251881Speter};
549251881Speter
550251881Speter
551251881Speter/* Baton for passing option/argument state to a subcommand function. */
552251881Speterstruct svnadmin_opt_state
553251881Speter{
554251881Speter  const char *repository_path;
555251881Speter  const char *fs_type;                              /* --fs-type */
556251881Speter  svn_version_t *compatible_version;                /* --compatible-version */
557251881Speter  svn_opt_revision_t start_revision, end_revision;  /* -r X[:Y] */
558251881Speter  const char *txn_id;                               /* -t TXN */
559251881Speter  svn_boolean_t help;                               /* --help or -? */
560251881Speter  svn_boolean_t version;                            /* --version */
561251881Speter  svn_boolean_t incremental;                        /* --incremental */
562251881Speter  svn_boolean_t use_deltas;                         /* --deltas */
563251881Speter  svn_boolean_t use_pre_commit_hook;                /* --use-pre-commit-hook */
564251881Speter  svn_boolean_t use_post_commit_hook;               /* --use-post-commit-hook */
565251881Speter  svn_boolean_t use_pre_revprop_change_hook;        /* --use-pre-revprop-change-hook */
566251881Speter  svn_boolean_t use_post_revprop_change_hook;       /* --use-post-revprop-change-hook */
567251881Speter  svn_boolean_t quiet;                              /* --quiet */
568251881Speter  svn_boolean_t bdb_txn_nosync;                     /* --bdb-txn-nosync */
569251881Speter  svn_boolean_t bdb_log_keep;                       /* --bdb-log-keep */
570251881Speter  svn_boolean_t clean_logs;                         /* --clean-logs */
571251881Speter  svn_boolean_t bypass_hooks;                       /* --bypass-hooks */
572251881Speter  svn_boolean_t wait;                               /* --wait */
573299742Sdim  svn_boolean_t keep_going;                         /* --keep-going */
574299742Sdim  svn_boolean_t check_normalization;                /* --check-normalization */
575299742Sdim  svn_boolean_t metadata_only;                      /* --metadata-only */
576251881Speter  svn_boolean_t bypass_prop_validation;             /* --bypass-prop-validation */
577299742Sdim  svn_boolean_t ignore_dates;                       /* --ignore-dates */
578251881Speter  enum svn_repos_load_uuid uuid_action;             /* --ignore-uuid,
579251881Speter                                                       --force-uuid */
580251881Speter  apr_uint64_t memory_cache_size;                   /* --memory-cache-size M */
581299742Sdim  const char *parent_dir;                           /* --parent-dir */
582251881Speter  svn_stringbuf_t *filedata;                        /* --file */
583251881Speter
584251881Speter  const char *config_dir;    /* Overriding Configuration Directory */
585251881Speter};
586251881Speter
587251881Speter
588251881Speter/* Set *REVNUM to the revision specified by REVISION (or to
589251881Speter   SVN_INVALID_REVNUM if that has the type 'unspecified'),
590251881Speter   possibly making use of the YOUNGEST revision number in REPOS. */
591251881Speterstatic svn_error_t *
592251881Speterget_revnum(svn_revnum_t *revnum, const svn_opt_revision_t *revision,
593251881Speter           svn_revnum_t youngest, svn_repos_t *repos, apr_pool_t *pool)
594251881Speter{
595251881Speter  if (revision->kind == svn_opt_revision_number)
596251881Speter    *revnum = revision->value.number;
597251881Speter  else if (revision->kind == svn_opt_revision_head)
598251881Speter    *revnum = youngest;
599251881Speter  else if (revision->kind == svn_opt_revision_date)
600251881Speter    SVN_ERR(svn_repos_dated_revision(revnum, repos, revision->value.date,
601251881Speter                                     pool));
602251881Speter  else if (revision->kind == svn_opt_revision_unspecified)
603251881Speter    *revnum = SVN_INVALID_REVNUM;
604251881Speter  else
605251881Speter    return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
606251881Speter                            _("Invalid revision specifier"));
607251881Speter
608251881Speter  if (*revnum > youngest)
609251881Speter    return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
610251881Speter       _("Revisions must not be greater than the youngest revision (%ld)"),
611251881Speter       youngest);
612251881Speter
613251881Speter  return SVN_NO_ERROR;
614251881Speter}
615251881Speter
616251881Speter/* Set *PATH to an internal-style, UTF8-encoded, local dirent path
617251881Speter   allocated from POOL and parsed from raw command-line argument ARG. */
618251881Speterstatic svn_error_t *
619251881Spetertarget_arg_to_dirent(const char **dirent,
620251881Speter                     const char *arg,
621251881Speter                     apr_pool_t *pool)
622251881Speter{
623251881Speter  const char *path;
624251881Speter
625251881Speter  SVN_ERR(svn_utf_cstring_to_utf8(&path, arg, pool));
626251881Speter  if (svn_path_is_url(path))
627251881Speter    return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
628299742Sdim                             _("Path '%s' is not a local path"), path);
629251881Speter  *dirent = svn_dirent_internal_style(path, pool);
630251881Speter  return SVN_NO_ERROR;
631251881Speter}
632251881Speter
633251881Speter/* Parse the remaining command-line arguments from OS, returning them
634251881Speter   in a new array *ARGS (allocated from POOL) and optionally verifying
635251881Speter   that we got the expected number thereof.  If MIN_EXPECTED is not
636251881Speter   negative, return an error if the function would return fewer than
637251881Speter   MIN_EXPECTED arguments.  If MAX_EXPECTED is not negative, return an
638251881Speter   error if the function would return more than MAX_EXPECTED
639251881Speter   arguments.
640251881Speter
641251881Speter   As a special case, when MIN_EXPECTED and MAX_EXPECTED are both 0,
642251881Speter   allow ARGS to be NULL.  */
643251881Speterstatic svn_error_t *
644251881Speterparse_args(apr_array_header_t **args,
645251881Speter           apr_getopt_t *os,
646251881Speter           int min_expected,
647251881Speter           int max_expected,
648251881Speter           apr_pool_t *pool)
649251881Speter{
650251881Speter  int num_args = os ? (os->argc - os->ind) : 0;
651251881Speter
652251881Speter  if (min_expected || max_expected)
653251881Speter    SVN_ERR_ASSERT(args);
654251881Speter
655251881Speter  if ((min_expected >= 0) && (num_args < min_expected))
656251881Speter    return svn_error_create(SVN_ERR_CL_INSUFFICIENT_ARGS, 0,
657299742Sdim                            _("Not enough arguments"));
658251881Speter  if ((max_expected >= 0) && (num_args > max_expected))
659251881Speter    return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, 0,
660299742Sdim                            _("Too many arguments"));
661251881Speter  if (args)
662251881Speter    {
663251881Speter      *args = apr_array_make(pool, num_args, sizeof(const char *));
664251881Speter
665251881Speter      if (num_args)
666251881Speter        while (os->ind < os->argc)
667251881Speter          APR_ARRAY_PUSH(*args, const char *) =
668251881Speter            apr_pstrdup(pool, os->argv[os->ind++]);
669251881Speter    }
670251881Speter
671251881Speter  return SVN_NO_ERROR;
672251881Speter}
673251881Speter
674251881Speter
675299742Sdim/* This implements 'svn_error_malfunction_handler_t. */
676299742Sdimstatic svn_error_t *
677299742Sdimcrashtest_malfunction_handler(svn_boolean_t can_return,
678299742Sdim                              const char *file,
679299742Sdim                              int line,
680299742Sdim                              const char *expr)
681299742Sdim{
682299742Sdim  abort();
683299742Sdim  return SVN_NO_ERROR; /* Not reached. */
684299742Sdim}
685299742Sdim
686251881Speter/* This implements `svn_opt_subcommand_t'. */
687251881Speterstatic svn_error_t *
688251881Spetersubcommand_crashtest(apr_getopt_t *os, void *baton, apr_pool_t *pool)
689251881Speter{
690251881Speter  struct svnadmin_opt_state *opt_state = baton;
691251881Speter  svn_repos_t *repos;
692251881Speter
693299742Sdim  (void)svn_error_set_malfunction_handler(crashtest_malfunction_handler);
694251881Speter  SVN_ERR(open_repos(&repos, opt_state->repository_path, pool));
695299742Sdim  SVN_ERR(svn_cmdline_printf(pool,
696299742Sdim                             _("Successfully opened repository '%s'.\n"
697299742Sdim                               "Will now crash to simulate a crashing "
698299742Sdim                               "server process.\n"),
699299742Sdim                             svn_dirent_local_style(opt_state->repository_path,
700299742Sdim                                                    pool)));
701251881Speter  SVN_ERR_MALFUNCTION();
702251881Speter
703251881Speter  /* merely silence a compiler warning (this will never be executed) */
704251881Speter  return SVN_NO_ERROR;
705251881Speter}
706251881Speter
707251881Speter/* This implements `svn_opt_subcommand_t'. */
708251881Speterstatic svn_error_t *
709251881Spetersubcommand_create(apr_getopt_t *os, void *baton, apr_pool_t *pool)
710251881Speter{
711251881Speter  struct svnadmin_opt_state *opt_state = baton;
712251881Speter  svn_repos_t *repos;
713251881Speter  apr_hash_t *fs_config = apr_hash_make(pool);
714251881Speter
715251881Speter  /* Expect no more arguments. */
716251881Speter  SVN_ERR(parse_args(NULL, os, 0, 0, pool));
717251881Speter
718251881Speter  svn_hash_sets(fs_config, SVN_FS_CONFIG_BDB_TXN_NOSYNC,
719251881Speter                (opt_state->bdb_txn_nosync ? "1" :"0"));
720251881Speter
721251881Speter  svn_hash_sets(fs_config, SVN_FS_CONFIG_BDB_LOG_AUTOREMOVE,
722251881Speter                (opt_state->bdb_log_keep ? "0" :"1"));
723251881Speter
724251881Speter  if (opt_state->fs_type)
725251881Speter    {
726251881Speter      /* With 1.8 we are announcing that BDB is deprecated.  No support
727251881Speter       * has been removed and it will continue to work until some future
728251881Speter       * date.  The purpose here is to discourage people from creating
729251881Speter       * new BDB repositories which they will need to dump/load into
730251881Speter       * FSFS or some new FS type in the future. */
731251881Speter      if (0 == strcmp(opt_state->fs_type, SVN_FS_TYPE_BDB))
732251881Speter        {
733251881Speter          SVN_ERR(svn_cmdline_fprintf(
734251881Speter                      stderr, pool,
735251881Speter                      _("%swarning:"
736251881Speter                        " The \"%s\" repository back-end is deprecated,"
737251881Speter                        " consider using \"%s\" instead.\n"),
738251881Speter                      "svnadmin: ", SVN_FS_TYPE_BDB, SVN_FS_TYPE_FSFS));
739251881Speter          fflush(stderr);
740251881Speter        }
741251881Speter      svn_hash_sets(fs_config, SVN_FS_CONFIG_FS_TYPE, opt_state->fs_type);
742251881Speter    }
743251881Speter
744251881Speter  if (opt_state->compatible_version)
745251881Speter    {
746251881Speter      if (! svn_version__at_least(opt_state->compatible_version, 1, 4, 0))
747251881Speter        svn_hash_sets(fs_config, SVN_FS_CONFIG_PRE_1_4_COMPATIBLE, "1");
748251881Speter      if (! svn_version__at_least(opt_state->compatible_version, 1, 5, 0))
749251881Speter        svn_hash_sets(fs_config, SVN_FS_CONFIG_PRE_1_5_COMPATIBLE, "1");
750251881Speter      if (! svn_version__at_least(opt_state->compatible_version, 1, 6, 0))
751251881Speter        svn_hash_sets(fs_config, SVN_FS_CONFIG_PRE_1_6_COMPATIBLE, "1");
752251881Speter      if (! svn_version__at_least(opt_state->compatible_version, 1, 8, 0))
753251881Speter        svn_hash_sets(fs_config, SVN_FS_CONFIG_PRE_1_8_COMPATIBLE, "1");
754299742Sdim      /* In 1.9, we figured out that we didn't have to keep extending this
755299742Sdim         madness indefinitely. */
756299742Sdim      svn_hash_sets(fs_config, SVN_FS_CONFIG_COMPATIBLE_VERSION,
757299742Sdim                    apr_psprintf(pool, "%d.%d.%d%s%s",
758299742Sdim                                 opt_state->compatible_version->major,
759299742Sdim                                 opt_state->compatible_version->minor,
760299742Sdim                                 opt_state->compatible_version->patch,
761299742Sdim                                 opt_state->compatible_version->tag
762299742Sdim                                 ? "-" : "",
763299742Sdim                                 opt_state->compatible_version->tag
764299742Sdim                                 ? opt_state->compatible_version->tag : ""));
765251881Speter    }
766251881Speter
767299742Sdim  if (opt_state->compatible_version)
768253734Speter    {
769299742Sdim      if (! svn_version__at_least(opt_state->compatible_version, 1, 1, 0)
770299742Sdim          /* ### TODO: this NULL check hard-codes knowledge of the library's
771299742Sdim                       default fs-type value */
772299742Sdim          && (opt_state->fs_type == NULL
773299742Sdim              || !strcmp(opt_state->fs_type, SVN_FS_TYPE_FSFS)))
774299742Sdim        {
775299742Sdim          return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
776299742Sdim                                  _("Repositories compatible with 1.0.x must "
777299742Sdim                                    "use --fs-type=bdb"));
778299742Sdim        }
779299742Sdim
780299742Sdim      if (! svn_version__at_least(opt_state->compatible_version, 1, 9, 0)
781299742Sdim          && opt_state->fs_type && !strcmp(opt_state->fs_type, SVN_FS_TYPE_FSX))
782299742Sdim        {
783299742Sdim          return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
784299742Sdim                                   _("Repositories compatible with 1.8.x or "
785299742Sdim                                     "earlier cannot use --fs-type=%s"),
786299742Sdim                                   SVN_FS_TYPE_FSX);
787299742Sdim        }
788253734Speter    }
789253734Speter
790251881Speter  SVN_ERR(svn_repos_create(&repos, opt_state->repository_path,
791251881Speter                           NULL, NULL, NULL, fs_config, pool));
792251881Speter  svn_fs_set_warning_func(svn_repos_fs(repos), warning_func, NULL);
793251881Speter  return SVN_NO_ERROR;
794251881Speter}
795251881Speter
796251881Speter
797251881Speter/* This implements `svn_opt_subcommand_t'. */
798251881Speterstatic svn_error_t *
799251881Spetersubcommand_deltify(apr_getopt_t *os, void *baton, apr_pool_t *pool)
800251881Speter{
801251881Speter  struct svnadmin_opt_state *opt_state = baton;
802251881Speter  svn_repos_t *repos;
803251881Speter  svn_fs_t *fs;
804251881Speter  svn_revnum_t start = SVN_INVALID_REVNUM, end = SVN_INVALID_REVNUM;
805251881Speter  svn_revnum_t youngest, revision;
806251881Speter  apr_pool_t *subpool = svn_pool_create(pool);
807251881Speter
808251881Speter  /* Expect no more arguments. */
809251881Speter  SVN_ERR(parse_args(NULL, os, 0, 0, pool));
810251881Speter
811251881Speter  SVN_ERR(open_repos(&repos, opt_state->repository_path, pool));
812251881Speter  fs = svn_repos_fs(repos);
813251881Speter  SVN_ERR(svn_fs_youngest_rev(&youngest, fs, pool));
814251881Speter
815251881Speter  /* Find the revision numbers at which to start and end. */
816251881Speter  SVN_ERR(get_revnum(&start, &opt_state->start_revision,
817251881Speter                     youngest, repos, pool));
818251881Speter  SVN_ERR(get_revnum(&end, &opt_state->end_revision,
819251881Speter                     youngest, repos, pool));
820251881Speter
821251881Speter  /* Fill in implied revisions if necessary. */
822251881Speter  if (start == SVN_INVALID_REVNUM)
823251881Speter    start = youngest;
824251881Speter  if (end == SVN_INVALID_REVNUM)
825251881Speter    end = start;
826251881Speter
827251881Speter  if (start > end)
828251881Speter    return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
829251881Speter       _("First revision cannot be higher than second"));
830251881Speter
831251881Speter  /* Loop over the requested revision range, performing the
832251881Speter     predecessor deltification on paths changed in each. */
833251881Speter  for (revision = start; revision <= end; revision++)
834251881Speter    {
835251881Speter      svn_pool_clear(subpool);
836251881Speter      SVN_ERR(check_cancel(NULL));
837251881Speter      if (! opt_state->quiet)
838251881Speter        SVN_ERR(svn_cmdline_printf(subpool, _("Deltifying revision %ld..."),
839251881Speter                                   revision));
840251881Speter      SVN_ERR(svn_fs_deltify_revision(fs, revision, subpool));
841251881Speter      if (! opt_state->quiet)
842251881Speter        SVN_ERR(svn_cmdline_printf(subpool, _("done.\n")));
843251881Speter    }
844251881Speter  svn_pool_destroy(subpool);
845251881Speter
846251881Speter  return SVN_NO_ERROR;
847251881Speter}
848251881Speter
849299742Sdim/* Structure for errors encountered during 'svnadmin verify --keep-going'. */
850299742Sdimstruct verification_error
851299742Sdim{
852299742Sdim  svn_revnum_t rev;
853299742Sdim  svn_error_t *err;
854299742Sdim};
855251881Speter
856299742Sdim/* Pool cleanup function to clear an svn_error_t *. */
857299742Sdimstatic apr_status_t
858299742Sdimerr_cleanup(void *data)
859299742Sdim{
860299742Sdim  svn_error_t *err = data;
861299742Sdim
862299742Sdim  svn_error_clear(err);
863299742Sdim
864299742Sdim  return APR_SUCCESS;
865299742Sdim}
866299742Sdim
867299742Sdimstruct repos_verify_callback_baton
868299742Sdim{
869299742Sdim  /* Should we continue after receiving a first verification error? */
870299742Sdim  svn_boolean_t keep_going;
871299742Sdim
872299742Sdim  /* List of errors encountered during 'svnadmin verify --keep-going'. */
873299742Sdim  apr_array_header_t *error_summary;
874299742Sdim
875299742Sdim  /* Pool for data collected during callback invocations. */
876299742Sdim  apr_pool_t *result_pool;
877299742Sdim};
878299742Sdim
879299742Sdim/* Implementation of svn_repos_verify_callback_t to handle errors coming
880299742Sdim   from svn_repos_verify_fs3(). */
881299742Sdimstatic svn_error_t *
882299742Sdimrepos_verify_callback(void *baton,
883299742Sdim                      svn_revnum_t revision,
884299742Sdim                      svn_error_t *verify_err,
885299742Sdim                      apr_pool_t *scratch_pool)
886299742Sdim{
887299742Sdim  struct repos_verify_callback_baton *b = baton;
888299742Sdim
889299742Sdim  if (revision == SVN_INVALID_REVNUM)
890299742Sdim    {
891299742Sdim      SVN_ERR(svn_cmdline_fputs(_("* Error verifying repository metadata.\n"),
892299742Sdim                                stderr, scratch_pool));
893299742Sdim    }
894299742Sdim  else
895299742Sdim    {
896299742Sdim      SVN_ERR(svn_cmdline_fprintf(stderr, scratch_pool,
897299742Sdim                                  _("* Error verifying revision %ld.\n"),
898299742Sdim                                  revision));
899299742Sdim    }
900299742Sdim
901299742Sdim  if (b->keep_going)
902299742Sdim    {
903299742Sdim      struct verification_error *verr;
904299742Sdim
905299742Sdim      svn_handle_error2(verify_err, stderr, FALSE, "svnadmin: ");
906299742Sdim
907299742Sdim      /* Remember the error in B->ERROR_SUMMARY. */
908299742Sdim      verr = apr_palloc(b->result_pool, sizeof(*verr));
909299742Sdim      verr->rev = revision;
910299742Sdim      verr->err = svn_error_dup(verify_err);
911299742Sdim      apr_pool_cleanup_register(b->result_pool, verr->err, err_cleanup,
912299742Sdim                                apr_pool_cleanup_null);
913299742Sdim      APR_ARRAY_PUSH(b->error_summary, struct verification_error *) = verr;
914299742Sdim
915299742Sdim      return SVN_NO_ERROR;
916299742Sdim    }
917299742Sdim  else
918299742Sdim    return svn_error_trace(svn_error_dup(verify_err));
919299742Sdim}
920299742Sdim
921251881Speter/* Implementation of svn_repos_notify_func_t to wrap the output to a
922299742Sdim   response stream for svn_repos_dump_fs2(), svn_repos_verify_fs(),
923299742Sdim   svn_repos_hotcopy3() and others. */
924251881Speterstatic void
925251881Speterrepos_notify_handler(void *baton,
926251881Speter                     const svn_repos_notify_t *notify,
927251881Speter                     apr_pool_t *scratch_pool)
928251881Speter{
929251881Speter  svn_stream_t *feedback_stream = baton;
930251881Speter
931251881Speter  switch (notify->action)
932251881Speter  {
933251881Speter    case svn_repos_notify_warning:
934262253Speter      svn_error_clear(svn_stream_printf(feedback_stream, scratch_pool,
935262253Speter                                        "WARNING 0x%04x: %s\n", notify->warning,
936262253Speter                                        notify->warning_str));
937251881Speter      return;
938251881Speter
939251881Speter    case svn_repos_notify_dump_rev_end:
940262253Speter      svn_error_clear(svn_stream_printf(feedback_stream, scratch_pool,
941262253Speter                                        _("* Dumped revision %ld.\n"),
942262253Speter                                        notify->revision));
943251881Speter      return;
944251881Speter
945251881Speter    case svn_repos_notify_verify_rev_end:
946262253Speter      svn_error_clear(svn_stream_printf(feedback_stream, scratch_pool,
947262253Speter                                        _("* Verified revision %ld.\n"),
948262253Speter                                        notify->revision));
949251881Speter      return;
950251881Speter
951251881Speter    case svn_repos_notify_verify_rev_structure:
952251881Speter      if (notify->revision == SVN_INVALID_REVNUM)
953299742Sdim        svn_error_clear(svn_stream_puts(feedback_stream,
954262253Speter                                _("* Verifying repository metadata ...\n")));
955251881Speter      else
956262253Speter        svn_error_clear(svn_stream_printf(feedback_stream, scratch_pool,
957262253Speter                        _("* Verifying metadata at revision %ld ...\n"),
958262253Speter                        notify->revision));
959251881Speter      return;
960251881Speter
961251881Speter    case svn_repos_notify_pack_shard_start:
962251881Speter      {
963251881Speter        const char *shardstr = apr_psprintf(scratch_pool,
964251881Speter                                            "%" APR_INT64_T_FMT,
965251881Speter                                            notify->shard);
966262253Speter        svn_error_clear(svn_stream_printf(feedback_stream, scratch_pool,
967262253Speter                                          _("Packing revisions in shard %s..."),
968262253Speter                                          shardstr));
969251881Speter      }
970251881Speter      return;
971251881Speter
972251881Speter    case svn_repos_notify_pack_shard_end:
973262253Speter      svn_error_clear(svn_stream_puts(feedback_stream, _("done.\n")));
974251881Speter      return;
975251881Speter
976251881Speter    case svn_repos_notify_pack_shard_start_revprop:
977251881Speter      {
978251881Speter        const char *shardstr = apr_psprintf(scratch_pool,
979251881Speter                                            "%" APR_INT64_T_FMT,
980251881Speter                                            notify->shard);
981262253Speter        svn_error_clear(svn_stream_printf(feedback_stream, scratch_pool,
982262253Speter                                          _("Packing revprops in shard %s..."),
983262253Speter                                          shardstr));
984251881Speter      }
985251881Speter      return;
986251881Speter
987251881Speter    case svn_repos_notify_pack_shard_end_revprop:
988262253Speter      svn_error_clear(svn_stream_puts(feedback_stream, _("done.\n")));
989251881Speter      return;
990251881Speter
991251881Speter    case svn_repos_notify_load_txn_committed:
992251881Speter      if (notify->old_revision == SVN_INVALID_REVNUM)
993251881Speter        {
994262253Speter          svn_error_clear(svn_stream_printf(feedback_stream, scratch_pool,
995262253Speter                            _("\n------- Committed revision %ld >>>\n\n"),
996262253Speter                            notify->new_revision));
997251881Speter        }
998251881Speter      else
999251881Speter        {
1000262253Speter          svn_error_clear(svn_stream_printf(feedback_stream, scratch_pool,
1001262253Speter                            _("\n------- Committed new rev %ld"
1002262253Speter                              " (loaded from original rev %ld"
1003262253Speter                              ") >>>\n\n"), notify->new_revision,
1004262253Speter                              notify->old_revision));
1005251881Speter        }
1006251881Speter      return;
1007251881Speter
1008251881Speter    case svn_repos_notify_load_node_start:
1009251881Speter      {
1010251881Speter        switch (notify->node_action)
1011251881Speter        {
1012251881Speter          case svn_node_action_change:
1013262253Speter            svn_error_clear(svn_stream_printf(feedback_stream, scratch_pool,
1014251881Speter                                  _("     * editing path : %s ..."),
1015262253Speter                                  notify->path));
1016251881Speter            break;
1017251881Speter
1018251881Speter          case svn_node_action_delete:
1019262253Speter            svn_error_clear(svn_stream_printf(feedback_stream, scratch_pool,
1020251881Speter                                  _("     * deleting path : %s ..."),
1021262253Speter                                  notify->path));
1022251881Speter            break;
1023251881Speter
1024251881Speter          case svn_node_action_add:
1025262253Speter            svn_error_clear(svn_stream_printf(feedback_stream, scratch_pool,
1026251881Speter                                  _("     * adding path : %s ..."),
1027262253Speter                                  notify->path));
1028251881Speter            break;
1029251881Speter
1030251881Speter          case svn_node_action_replace:
1031262253Speter            svn_error_clear(svn_stream_printf(feedback_stream, scratch_pool,
1032251881Speter                                  _("     * replacing path : %s ..."),
1033262253Speter                                  notify->path));
1034251881Speter            break;
1035251881Speter
1036251881Speter        }
1037251881Speter      }
1038251881Speter      return;
1039251881Speter
1040251881Speter    case svn_repos_notify_load_node_done:
1041299742Sdim      svn_error_clear(svn_stream_puts(feedback_stream, _(" done.\n")));
1042251881Speter      return;
1043251881Speter
1044251881Speter    case svn_repos_notify_load_copied_node:
1045299742Sdim      svn_error_clear(svn_stream_puts(feedback_stream, "COPIED..."));
1046251881Speter      return;
1047251881Speter
1048251881Speter    case svn_repos_notify_load_txn_start:
1049262253Speter      svn_error_clear(svn_stream_printf(feedback_stream, scratch_pool,
1050262253Speter                                _("<<< Started new transaction, based on "
1051262253Speter                                  "original revision %ld\n"),
1052262253Speter                                notify->old_revision));
1053251881Speter      return;
1054251881Speter
1055251881Speter    case svn_repos_notify_load_skipped_rev:
1056262253Speter      svn_error_clear(svn_stream_printf(feedback_stream, scratch_pool,
1057262253Speter                                _("<<< Skipped original revision %ld\n"),
1058262253Speter                                notify->old_revision));
1059251881Speter      return;
1060251881Speter
1061251881Speter    case svn_repos_notify_load_normalized_mergeinfo:
1062262253Speter      svn_error_clear(svn_stream_printf(feedback_stream, scratch_pool,
1063262253Speter                                _(" removing '\\r' from %s ..."),
1064262253Speter                                SVN_PROP_MERGEINFO));
1065251881Speter      return;
1066251881Speter
1067251881Speter    case svn_repos_notify_mutex_acquired:
1068251881Speter      /* Enable cancellation signal handlers. */
1069251881Speter      setup_cancellation_signals(signal_handler);
1070251881Speter      return;
1071251881Speter
1072251881Speter    case svn_repos_notify_recover_start:
1073299742Sdim      svn_error_clear(svn_stream_puts(feedback_stream,
1074262253Speter                             _("Repository lock acquired.\n"
1075262253Speter                               "Please wait; recovering the"
1076262253Speter                               " repository may take some time...\n")));
1077251881Speter      return;
1078251881Speter
1079251881Speter    case svn_repos_notify_upgrade_start:
1080262253Speter      svn_error_clear(svn_stream_puts(feedback_stream,
1081262253Speter                             _("Repository lock acquired.\n"
1082262253Speter                               "Please wait; upgrading the"
1083262253Speter                               " repository may take some time...\n")));
1084251881Speter      return;
1085251881Speter
1086299742Sdim    case svn_repos_notify_pack_revprops:
1087299742Sdim      {
1088299742Sdim        const char *shardstr = apr_psprintf(scratch_pool,
1089299742Sdim                                            "%" APR_INT64_T_FMT,
1090299742Sdim                                            notify->shard);
1091299742Sdim        svn_error_clear(svn_stream_printf(feedback_stream, scratch_pool,
1092299742Sdim                              _("Packed revision properties in shard %s\n"),
1093299742Sdim                              shardstr));
1094299742Sdim        return;
1095299742Sdim      }
1096299742Sdim
1097299742Sdim    case svn_repos_notify_cleanup_revprops:
1098299742Sdim      {
1099299742Sdim        const char *shardstr = apr_psprintf(scratch_pool,
1100299742Sdim                                            "%" APR_INT64_T_FMT,
1101299742Sdim                                            notify->shard);
1102299742Sdim        svn_error_clear(svn_stream_printf(feedback_stream, scratch_pool,
1103299742Sdim                              _("Removed non-packed revision properties"
1104299742Sdim                                " in shard %s\n"),
1105299742Sdim                              shardstr));
1106299742Sdim        return;
1107299742Sdim      }
1108299742Sdim
1109299742Sdim    case svn_repos_notify_format_bumped:
1110299742Sdim      svn_error_clear(svn_stream_printf(feedback_stream, scratch_pool,
1111299742Sdim                            _("Bumped repository format to %ld\n"),
1112299742Sdim                            notify->revision));
1113299742Sdim      return;
1114299742Sdim
1115299742Sdim    case svn_repos_notify_hotcopy_rev_range:
1116299742Sdim      if (notify->start_revision == notify->end_revision)
1117299742Sdim        {
1118299742Sdim          svn_error_clear(svn_stream_printf(feedback_stream, scratch_pool,
1119299742Sdim                                            _("* Copied revision %ld.\n"),
1120299742Sdim                                            notify->start_revision));
1121299742Sdim        }
1122299742Sdim      else
1123299742Sdim        {
1124299742Sdim          svn_error_clear(svn_stream_printf(feedback_stream, scratch_pool,
1125299742Sdim                               _("* Copied revisions from %ld to %ld.\n"),
1126299742Sdim                               notify->start_revision, notify->end_revision));
1127299742Sdim        }
1128299742Sdim
1129251881Speter    default:
1130251881Speter      return;
1131251881Speter  }
1132251881Speter}
1133251881Speter
1134251881Speter
1135251881Speter/* Baton for recode_write(). */
1136251881Speterstruct recode_write_baton
1137251881Speter{
1138251881Speter  apr_pool_t *pool;
1139251881Speter  FILE *out;
1140251881Speter};
1141251881Speter
1142251881Speter/* This implements the 'svn_write_fn_t' interface.
1143251881Speter
1144251881Speter   Write DATA to ((struct recode_write_baton *) BATON)->out, in the
1145251881Speter   console encoding, using svn_cmdline_fprintf().  DATA is a
1146251881Speter   UTF8-encoded C string, therefore ignore LEN.
1147251881Speter
1148251881Speter   ### This recoding mechanism might want to be abstracted into
1149251881Speter   ### svn_io.h or svn_cmdline.h, if it proves useful elsewhere. */
1150251881Speterstatic svn_error_t *recode_write(void *baton,
1151251881Speter                                 const char *data,
1152251881Speter                                 apr_size_t *len)
1153251881Speter{
1154251881Speter  struct recode_write_baton *rwb = baton;
1155251881Speter  svn_pool_clear(rwb->pool);
1156251881Speter  return svn_cmdline_fputs(data, rwb->out, rwb->pool);
1157251881Speter}
1158251881Speter
1159251881Speter/* Create a stream, to write to STD_STREAM, that uses recode_write()
1160251881Speter   to perform UTF-8 to console encoding translation. */
1161251881Speterstatic svn_stream_t *
1162251881Speterrecode_stream_create(FILE *std_stream, apr_pool_t *pool)
1163251881Speter{
1164251881Speter  struct recode_write_baton *std_stream_rwb =
1165251881Speter    apr_palloc(pool, sizeof(struct recode_write_baton));
1166251881Speter
1167251881Speter  svn_stream_t *rw_stream = svn_stream_create(std_stream_rwb, pool);
1168251881Speter  std_stream_rwb->pool = svn_pool_create(pool);
1169251881Speter  std_stream_rwb->out = std_stream;
1170251881Speter  svn_stream_set_write(rw_stream, recode_write);
1171251881Speter  return rw_stream;
1172251881Speter}
1173251881Speter
1174251881Speter
1175251881Speter/* This implements `svn_opt_subcommand_t'. */
1176251881Speterstatic svn_error_t *
1177251881Spetersubcommand_dump(apr_getopt_t *os, void *baton, apr_pool_t *pool)
1178251881Speter{
1179251881Speter  struct svnadmin_opt_state *opt_state = baton;
1180251881Speter  svn_repos_t *repos;
1181251881Speter  svn_fs_t *fs;
1182251881Speter  svn_stream_t *stdout_stream;
1183251881Speter  svn_revnum_t lower = SVN_INVALID_REVNUM, upper = SVN_INVALID_REVNUM;
1184251881Speter  svn_revnum_t youngest;
1185299742Sdim  svn_stream_t *feedback_stream = NULL;
1186251881Speter
1187251881Speter  /* Expect no more arguments. */
1188251881Speter  SVN_ERR(parse_args(NULL, os, 0, 0, pool));
1189251881Speter
1190251881Speter  SVN_ERR(open_repos(&repos, opt_state->repository_path, pool));
1191251881Speter  fs = svn_repos_fs(repos);
1192251881Speter  SVN_ERR(svn_fs_youngest_rev(&youngest, fs, pool));
1193251881Speter
1194251881Speter  /* Find the revision numbers at which to start and end. */
1195251881Speter  SVN_ERR(get_revnum(&lower, &opt_state->start_revision,
1196251881Speter                     youngest, repos, pool));
1197251881Speter  SVN_ERR(get_revnum(&upper, &opt_state->end_revision,
1198251881Speter                     youngest, repos, pool));
1199251881Speter
1200251881Speter  /* Fill in implied revisions if necessary. */
1201251881Speter  if (lower == SVN_INVALID_REVNUM)
1202251881Speter    {
1203251881Speter      lower = 0;
1204251881Speter      upper = youngest;
1205251881Speter    }
1206251881Speter  else if (upper == SVN_INVALID_REVNUM)
1207251881Speter    {
1208251881Speter      upper = lower;
1209251881Speter    }
1210251881Speter
1211251881Speter  if (lower > upper)
1212251881Speter    return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
1213251881Speter       _("First revision cannot be higher than second"));
1214251881Speter
1215251881Speter  SVN_ERR(svn_stream_for_stdout(&stdout_stream, pool));
1216251881Speter
1217251881Speter  /* Progress feedback goes to STDERR, unless they asked to suppress it. */
1218251881Speter  if (! opt_state->quiet)
1219299742Sdim    feedback_stream = recode_stream_create(stderr, pool);
1220251881Speter
1221251881Speter  SVN_ERR(svn_repos_dump_fs3(repos, stdout_stream, lower, upper,
1222251881Speter                             opt_state->incremental, opt_state->use_deltas,
1223251881Speter                             !opt_state->quiet ? repos_notify_handler : NULL,
1224299742Sdim                             feedback_stream, check_cancel, NULL, pool));
1225251881Speter
1226251881Speter  return SVN_NO_ERROR;
1227251881Speter}
1228251881Speter
1229251881Speterstruct freeze_baton_t {
1230251881Speter  const char *command;
1231251881Speter  const char **args;
1232251881Speter  int status;
1233251881Speter};
1234251881Speter
1235251881Speter/* Implements svn_repos_freeze_func_t */
1236251881Speterstatic svn_error_t *
1237251881Speterfreeze_body(void *baton,
1238251881Speter            apr_pool_t *pool)
1239251881Speter{
1240251881Speter  struct freeze_baton_t *b = baton;
1241251881Speter  apr_status_t apr_err;
1242251881Speter  apr_file_t *infile, *outfile, *errfile;
1243251881Speter
1244251881Speter  apr_err = apr_file_open_stdin(&infile, pool);
1245251881Speter  if (apr_err)
1246251881Speter    return svn_error_wrap_apr(apr_err, "Can't open stdin");
1247251881Speter  apr_err = apr_file_open_stdout(&outfile, pool);
1248251881Speter  if (apr_err)
1249251881Speter    return svn_error_wrap_apr(apr_err, "Can't open stdout");
1250251881Speter  apr_err = apr_file_open_stderr(&errfile, pool);
1251251881Speter  if (apr_err)
1252251881Speter    return svn_error_wrap_apr(apr_err, "Can't open stderr");
1253251881Speter
1254251881Speter  SVN_ERR(svn_io_run_cmd(NULL, b->command, b->args, &b->status,
1255251881Speter                         NULL, TRUE,
1256251881Speter                         infile, outfile, errfile, pool));
1257251881Speter
1258251881Speter  return SVN_NO_ERROR;
1259251881Speter}
1260251881Speter
1261251881Speterstatic svn_error_t *
1262251881Spetersubcommand_freeze(apr_getopt_t *os, void *baton, apr_pool_t *pool)
1263251881Speter{
1264251881Speter  struct svnadmin_opt_state *opt_state = baton;
1265251881Speter  apr_array_header_t *paths;
1266251881Speter  apr_array_header_t *args;
1267251881Speter  int i;
1268251881Speter  struct freeze_baton_t b;
1269251881Speter
1270251881Speter  SVN_ERR(svn_opt_parse_all_args(&args, os, pool));
1271251881Speter
1272251881Speter  if (!args->nelts)
1273251881Speter    return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, 0,
1274251881Speter                            _("No program provided"));
1275251881Speter
1276251881Speter  if (!opt_state->filedata)
1277251881Speter    {
1278251881Speter      /* One repository on the command line. */
1279251881Speter      paths = apr_array_make(pool, 1, sizeof(const char *));
1280251881Speter      APR_ARRAY_PUSH(paths, const char *) = opt_state->repository_path;
1281251881Speter    }
1282251881Speter  else
1283251881Speter    {
1284299742Sdim      const char *utf8;
1285251881Speter      /* All repositories in filedata. */
1286299742Sdim      SVN_ERR(svn_utf_cstring_to_utf8(&utf8, opt_state->filedata->data, pool));
1287299742Sdim      paths = svn_cstring_split(utf8, "\r\n", FALSE, pool);
1288251881Speter    }
1289251881Speter
1290251881Speter  b.command = APR_ARRAY_IDX(args, 0, const char *);
1291289166Speter  b.args = apr_palloc(pool, sizeof(char *) * (args->nelts + 1));
1292251881Speter  for (i = 0; i < args->nelts; ++i)
1293251881Speter    b.args[i] = APR_ARRAY_IDX(args, i, const char *);
1294251881Speter  b.args[args->nelts] = NULL;
1295251881Speter
1296251881Speter  SVN_ERR(svn_repos_freeze(paths, freeze_body, &b, pool));
1297251881Speter
1298251881Speter  /* Make any non-zero status visible to the user. */
1299251881Speter  if (b.status)
1300251881Speter    exit(b.status);
1301251881Speter
1302251881Speter  return SVN_NO_ERROR;
1303251881Speter}
1304251881Speter
1305251881Speter
1306251881Speter/* This implements `svn_opt_subcommand_t'. */
1307251881Speterstatic svn_error_t *
1308251881Spetersubcommand_help(apr_getopt_t *os, void *baton, apr_pool_t *pool)
1309251881Speter{
1310251881Speter  struct svnadmin_opt_state *opt_state = baton;
1311251881Speter  const char *header =
1312251881Speter    _("general usage: svnadmin SUBCOMMAND REPOS_PATH  [ARGS & OPTIONS ...]\n"
1313299742Sdim      "Subversion repository administration tool.\n"
1314251881Speter      "Type 'svnadmin help <subcommand>' for help on a specific subcommand.\n"
1315251881Speter      "Type 'svnadmin --version' to see the program version and FS modules.\n"
1316251881Speter      "\n"
1317251881Speter      "Available subcommands:\n");
1318251881Speter
1319251881Speter  const char *fs_desc_start
1320251881Speter    = _("The following repository back-end (FS) modules are available:\n\n");
1321251881Speter
1322251881Speter  svn_stringbuf_t *version_footer;
1323251881Speter
1324251881Speter  version_footer = svn_stringbuf_create(fs_desc_start, pool);
1325251881Speter  SVN_ERR(svn_fs_print_modules(version_footer, pool));
1326251881Speter
1327251881Speter  SVN_ERR(svn_opt_print_help4(os, "svnadmin",
1328251881Speter                              opt_state ? opt_state->version : FALSE,
1329251881Speter                              opt_state ? opt_state->quiet : FALSE,
1330251881Speter                              /*###opt_state ? opt_state->verbose :*/ FALSE,
1331251881Speter                              version_footer->data,
1332251881Speter                              header, cmd_table, options_table, NULL, NULL,
1333251881Speter                              pool));
1334251881Speter
1335251881Speter  return SVN_NO_ERROR;
1336251881Speter}
1337251881Speter
1338251881Speter
1339251881Speter/* Set *REVNUM to the revision number of a numeric REV, or to
1340251881Speter   SVN_INVALID_REVNUM if REV is unspecified. */
1341251881Speterstatic svn_error_t *
1342251881Speteroptrev_to_revnum(svn_revnum_t *revnum, const svn_opt_revision_t *opt_rev)
1343251881Speter{
1344251881Speter  if (opt_rev->kind == svn_opt_revision_number)
1345251881Speter    {
1346251881Speter      *revnum = opt_rev->value.number;
1347251881Speter      if (! SVN_IS_VALID_REVNUM(*revnum))
1348251881Speter        return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
1349251881Speter                                 _("Invalid revision number (%ld) specified"),
1350251881Speter                                 *revnum);
1351251881Speter    }
1352251881Speter  else if (opt_rev->kind == svn_opt_revision_unspecified)
1353251881Speter    {
1354251881Speter      *revnum = SVN_INVALID_REVNUM;
1355251881Speter    }
1356251881Speter  else
1357251881Speter    {
1358251881Speter      return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
1359251881Speter                              _("Non-numeric revision specified"));
1360251881Speter    }
1361251881Speter  return SVN_NO_ERROR;
1362251881Speter}
1363251881Speter
1364251881Speter
1365251881Speter/* This implements `svn_opt_subcommand_t'. */
1366251881Speterstatic svn_error_t *
1367251881Spetersubcommand_load(apr_getopt_t *os, void *baton, apr_pool_t *pool)
1368251881Speter{
1369251881Speter  svn_error_t *err;
1370251881Speter  struct svnadmin_opt_state *opt_state = baton;
1371251881Speter  svn_repos_t *repos;
1372251881Speter  svn_revnum_t lower = SVN_INVALID_REVNUM, upper = SVN_INVALID_REVNUM;
1373299742Sdim  svn_stream_t *stdin_stream;
1374299742Sdim  svn_stream_t *feedback_stream = NULL;
1375251881Speter
1376251881Speter  /* Expect no more arguments. */
1377251881Speter  SVN_ERR(parse_args(NULL, os, 0, 0, pool));
1378251881Speter
1379251881Speter  /* Find the revision numbers at which to start and end.  We only
1380251881Speter     support a limited set of revision kinds: number and unspecified. */
1381251881Speter  SVN_ERR(optrev_to_revnum(&lower, &opt_state->start_revision));
1382251881Speter  SVN_ERR(optrev_to_revnum(&upper, &opt_state->end_revision));
1383251881Speter
1384251881Speter  /* Fill in implied revisions if necessary. */
1385251881Speter  if ((upper == SVN_INVALID_REVNUM) && (lower != SVN_INVALID_REVNUM))
1386251881Speter    {
1387251881Speter      upper = lower;
1388251881Speter    }
1389251881Speter  else if ((upper != SVN_INVALID_REVNUM) && (lower == SVN_INVALID_REVNUM))
1390251881Speter    {
1391251881Speter      lower = upper;
1392251881Speter    }
1393251881Speter
1394251881Speter  /* Ensure correct range ordering. */
1395251881Speter  if (lower > upper)
1396251881Speter    {
1397251881Speter      return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
1398251881Speter                              _("First revision cannot be higher than second"));
1399251881Speter    }
1400251881Speter
1401251881Speter  SVN_ERR(open_repos(&repos, opt_state->repository_path, pool));
1402251881Speter
1403251881Speter  /* Read the stream from STDIN.  Users can redirect a file. */
1404251881Speter  SVN_ERR(svn_stream_for_stdin(&stdin_stream, pool));
1405251881Speter
1406251881Speter  /* Progress feedback goes to STDOUT, unless they asked to suppress it. */
1407251881Speter  if (! opt_state->quiet)
1408299742Sdim    feedback_stream = recode_stream_create(stdout, pool);
1409251881Speter
1410299742Sdim  err = svn_repos_load_fs5(repos, stdin_stream, lower, upper,
1411251881Speter                           opt_state->uuid_action, opt_state->parent_dir,
1412251881Speter                           opt_state->use_pre_commit_hook,
1413251881Speter                           opt_state->use_post_commit_hook,
1414251881Speter                           !opt_state->bypass_prop_validation,
1415299742Sdim                           opt_state->ignore_dates,
1416251881Speter                           opt_state->quiet ? NULL : repos_notify_handler,
1417299742Sdim                           feedback_stream, check_cancel, NULL, pool);
1418251881Speter  if (err && err->apr_err == SVN_ERR_BAD_PROPERTY_VALUE)
1419251881Speter    return svn_error_quick_wrap(err,
1420251881Speter                                _("Invalid property value found in "
1421251881Speter                                  "dumpstream; consider repairing the source "
1422251881Speter                                  "or using --bypass-prop-validation while "
1423251881Speter                                  "loading."));
1424251881Speter  return err;
1425251881Speter}
1426251881Speter
1427251881Speter
1428251881Speter/* This implements `svn_opt_subcommand_t'. */
1429251881Speterstatic svn_error_t *
1430251881Spetersubcommand_lstxns(apr_getopt_t *os, void *baton, apr_pool_t *pool)
1431251881Speter{
1432251881Speter  struct svnadmin_opt_state *opt_state = baton;
1433251881Speter  svn_repos_t *repos;
1434251881Speter  svn_fs_t *fs;
1435251881Speter  apr_array_header_t *txns;
1436251881Speter  int i;
1437251881Speter
1438251881Speter  /* Expect no more arguments. */
1439251881Speter  SVN_ERR(parse_args(NULL, os, 0, 0, pool));
1440251881Speter
1441251881Speter  SVN_ERR(open_repos(&repos, opt_state->repository_path, pool));
1442251881Speter  fs = svn_repos_fs(repos);
1443251881Speter  SVN_ERR(svn_fs_list_transactions(&txns, fs, pool));
1444251881Speter
1445251881Speter  /* Loop, printing revisions. */
1446251881Speter  for (i = 0; i < txns->nelts; i++)
1447251881Speter    {
1448251881Speter      SVN_ERR(svn_cmdline_printf(pool, "%s\n",
1449251881Speter                                 APR_ARRAY_IDX(txns, i, const char *)));
1450251881Speter    }
1451251881Speter
1452251881Speter  return SVN_NO_ERROR;
1453251881Speter}
1454251881Speter
1455251881Speter
1456251881Speter/* This implements `svn_opt_subcommand_t'. */
1457251881Speterstatic svn_error_t *
1458251881Spetersubcommand_recover(apr_getopt_t *os, void *baton, apr_pool_t *pool)
1459251881Speter{
1460251881Speter  svn_revnum_t youngest_rev;
1461251881Speter  svn_repos_t *repos;
1462251881Speter  svn_error_t *err;
1463251881Speter  struct svnadmin_opt_state *opt_state = baton;
1464299742Sdim  svn_stream_t *feedback_stream = NULL;
1465251881Speter
1466251881Speter  /* Expect no more arguments. */
1467251881Speter  SVN_ERR(parse_args(NULL, os, 0, 0, pool));
1468251881Speter
1469299742Sdim  SVN_ERR(svn_stream_for_stdout(&feedback_stream, pool));
1470251881Speter
1471251881Speter  /* Restore default signal handlers until after we have acquired the
1472251881Speter   * exclusive lock so that the user interrupt before we actually
1473251881Speter   * touch the repository. */
1474251881Speter  setup_cancellation_signals(SIG_DFL);
1475251881Speter
1476251881Speter  err = svn_repos_recover4(opt_state->repository_path, TRUE,
1477299742Sdim                           repos_notify_handler, feedback_stream,
1478251881Speter                           check_cancel, NULL, pool);
1479251881Speter  if (err)
1480251881Speter    {
1481251881Speter      if (! APR_STATUS_IS_EAGAIN(err->apr_err))
1482251881Speter        return err;
1483251881Speter      svn_error_clear(err);
1484251881Speter      if (! opt_state->wait)
1485251881Speter        return svn_error_create(SVN_ERR_REPOS_LOCKED, NULL,
1486251881Speter                                _("Failed to get exclusive repository "
1487251881Speter                                  "access; perhaps another process\n"
1488251881Speter                                  "such as httpd, svnserve or svn "
1489251881Speter                                  "has it open?"));
1490251881Speter      SVN_ERR(svn_cmdline_printf(pool,
1491251881Speter                                 _("Waiting on repository lock; perhaps"
1492251881Speter                                   " another process has it open?\n")));
1493251881Speter      SVN_ERR(svn_cmdline_fflush(stdout));
1494251881Speter      SVN_ERR(svn_repos_recover4(opt_state->repository_path, FALSE,
1495299742Sdim                                 repos_notify_handler, feedback_stream,
1496251881Speter                                 check_cancel, NULL, pool));
1497251881Speter    }
1498251881Speter
1499251881Speter  SVN_ERR(svn_cmdline_printf(pool, _("\nRecovery completed.\n")));
1500251881Speter
1501251881Speter  /* Since db transactions may have been replayed, it's nice to tell
1502251881Speter     people what the latest revision is.  It also proves that the
1503251881Speter     recovery actually worked. */
1504251881Speter  SVN_ERR(open_repos(&repos, opt_state->repository_path, pool));
1505251881Speter  SVN_ERR(svn_fs_youngest_rev(&youngest_rev, svn_repos_fs(repos), pool));
1506251881Speter  SVN_ERR(svn_cmdline_printf(pool, _("The latest repos revision is %ld.\n"),
1507251881Speter                             youngest_rev));
1508251881Speter
1509251881Speter  return SVN_NO_ERROR;
1510251881Speter}
1511251881Speter
1512251881Speter
1513251881Speter/* This implements `svn_opt_subcommand_t'. */
1514251881Speterstatic svn_error_t *
1515251881Speterlist_dblogs(apr_getopt_t *os, void *baton, svn_boolean_t only_unused,
1516251881Speter            apr_pool_t *pool)
1517251881Speter{
1518251881Speter  struct svnadmin_opt_state *opt_state = baton;
1519251881Speter  apr_array_header_t *logfiles;
1520251881Speter  int i;
1521251881Speter
1522251881Speter  /* Expect no more arguments. */
1523251881Speter  SVN_ERR(parse_args(NULL, os, 0, 0, pool));
1524251881Speter
1525251881Speter  SVN_ERR(svn_repos_db_logfiles(&logfiles,
1526251881Speter                                opt_state->repository_path,
1527251881Speter                                only_unused,
1528251881Speter                                pool));
1529251881Speter
1530251881Speter  /* Loop, printing log files.  We append the log paths to the
1531251881Speter     repository path, making sure to return everything to the native
1532251881Speter     style before printing. */
1533251881Speter  for (i = 0; i < logfiles->nelts; i++)
1534251881Speter    {
1535251881Speter      const char *log_utf8;
1536251881Speter      log_utf8 = svn_dirent_join(opt_state->repository_path,
1537251881Speter                                 APR_ARRAY_IDX(logfiles, i, const char *),
1538251881Speter                                 pool);
1539251881Speter      log_utf8 = svn_dirent_local_style(log_utf8, pool);
1540251881Speter      SVN_ERR(svn_cmdline_printf(pool, "%s\n", log_utf8));
1541251881Speter    }
1542251881Speter
1543251881Speter  return SVN_NO_ERROR;
1544251881Speter}
1545251881Speter
1546251881Speter
1547251881Speter/* This implements `svn_opt_subcommand_t'. */
1548251881Speterstatic svn_error_t *
1549251881Spetersubcommand_list_dblogs(apr_getopt_t *os, void *baton, apr_pool_t *pool)
1550251881Speter{
1551251881Speter  SVN_ERR(list_dblogs(os, baton, FALSE, pool));
1552251881Speter  return SVN_NO_ERROR;
1553251881Speter}
1554251881Speter
1555251881Speter
1556251881Speter/* This implements `svn_opt_subcommand_t'. */
1557251881Speterstatic svn_error_t *
1558251881Spetersubcommand_list_unused_dblogs(apr_getopt_t *os, void *baton, apr_pool_t *pool)
1559251881Speter{
1560251881Speter  /* Expect no more arguments. */
1561251881Speter  SVN_ERR(parse_args(NULL, os, 0, 0, pool));
1562251881Speter
1563251881Speter  SVN_ERR(list_dblogs(os, baton, TRUE, pool));
1564251881Speter  return SVN_NO_ERROR;
1565251881Speter}
1566251881Speter
1567251881Speter
1568251881Speter/* This implements `svn_opt_subcommand_t'. */
1569251881Speterstatic svn_error_t *
1570251881Spetersubcommand_rmtxns(apr_getopt_t *os, void *baton, apr_pool_t *pool)
1571251881Speter{
1572251881Speter  struct svnadmin_opt_state *opt_state = baton;
1573251881Speter  svn_repos_t *repos;
1574251881Speter  svn_fs_t *fs;
1575251881Speter  svn_fs_txn_t *txn;
1576251881Speter  apr_array_header_t *args;
1577251881Speter  int i;
1578251881Speter  apr_pool_t *subpool = svn_pool_create(pool);
1579251881Speter
1580251881Speter  SVN_ERR(svn_opt_parse_all_args(&args, os, pool));
1581251881Speter
1582251881Speter  SVN_ERR(open_repos(&repos, opt_state->repository_path, pool));
1583251881Speter  fs = svn_repos_fs(repos);
1584251881Speter
1585251881Speter  /* All the rest of the arguments are transaction names. */
1586251881Speter  for (i = 0; i < args->nelts; i++)
1587251881Speter    {
1588251881Speter      const char *txn_name = APR_ARRAY_IDX(args, i, const char *);
1589251881Speter      const char *txn_name_utf8;
1590251881Speter      svn_error_t *err;
1591251881Speter
1592251881Speter      svn_pool_clear(subpool);
1593251881Speter
1594251881Speter      SVN_ERR(svn_utf_cstring_to_utf8(&txn_name_utf8, txn_name, subpool));
1595251881Speter
1596251881Speter      /* Try to open the txn.  If that succeeds, try to abort it. */
1597251881Speter      err = svn_fs_open_txn(&txn, fs, txn_name_utf8, subpool);
1598251881Speter      if (! err)
1599251881Speter        err = svn_fs_abort_txn(txn, subpool);
1600251881Speter
1601251881Speter      /* If either the open or the abort of the txn fails because that
1602251881Speter         transaction is dead, just try to purge the thing.  Else,
1603251881Speter         there was either an error worth reporting, or not error at
1604251881Speter         all.  */
1605251881Speter      if (err && (err->apr_err == SVN_ERR_FS_TRANSACTION_DEAD))
1606251881Speter        {
1607251881Speter          svn_error_clear(err);
1608251881Speter          err = svn_fs_purge_txn(fs, txn_name_utf8, subpool);
1609251881Speter        }
1610251881Speter
1611251881Speter      /* If we had a real from the txn open, abort, or purge, we clear
1612251881Speter         that error and just report to the user that we had an issue
1613251881Speter         with this particular txn. */
1614251881Speter      if (err)
1615251881Speter        {
1616251881Speter          svn_handle_error2(err, stderr, FALSE /* non-fatal */, "svnadmin: ");
1617251881Speter          svn_error_clear(err);
1618251881Speter        }
1619251881Speter      else if (! opt_state->quiet)
1620251881Speter        {
1621251881Speter          SVN_ERR(svn_cmdline_printf(subpool, _("Transaction '%s' removed.\n"),
1622251881Speter                                     txn_name));
1623251881Speter        }
1624251881Speter    }
1625251881Speter
1626251881Speter  svn_pool_destroy(subpool);
1627251881Speter
1628251881Speter  return SVN_NO_ERROR;
1629251881Speter}
1630251881Speter
1631251881Speter
1632251881Speter/* A helper for the 'setrevprop' and 'setlog' commands.  Expects
1633299742Sdim   OPT_STATE->txn_id, OPT_STATE->use_pre_revprop_change_hook and
1634299742Sdim   OPT_STATE->use_post_revprop_change_hook to be set appropriately.
1635299742Sdim   If FILENAME is NULL, delete property PROP_NAME.  */
1636251881Speterstatic svn_error_t *
1637251881Speterset_revprop(const char *prop_name, const char *filename,
1638251881Speter            struct svnadmin_opt_state *opt_state, apr_pool_t *pool)
1639251881Speter{
1640251881Speter  svn_repos_t *repos;
1641299742Sdim  svn_string_t *prop_value;
1642251881Speter
1643299742Sdim  if (filename)
1644299742Sdim    {
1645299742Sdim      svn_stringbuf_t *file_contents;
1646251881Speter
1647299742Sdim      SVN_ERR(svn_stringbuf_from_file2(&file_contents, filename, pool));
1648251881Speter
1649299742Sdim      prop_value = svn_string_create_empty(pool);
1650299742Sdim      prop_value->data = file_contents->data;
1651299742Sdim      prop_value->len = file_contents->len;
1652251881Speter
1653299742Sdim      SVN_ERR(svn_subst_translate_string2(&prop_value, NULL, NULL, prop_value,
1654299742Sdim                                          NULL, FALSE, pool, pool));
1655299742Sdim    }
1656299742Sdim  else
1657299742Sdim    {
1658299742Sdim      prop_value = NULL;
1659299742Sdim    }
1660299742Sdim
1661251881Speter  /* Open the filesystem  */
1662251881Speter  SVN_ERR(open_repos(&repos, opt_state->repository_path, pool));
1663251881Speter
1664299742Sdim  if (opt_state->txn_id)
1665299742Sdim    {
1666299742Sdim      svn_fs_t *fs = svn_repos_fs(repos);
1667299742Sdim      svn_fs_txn_t *txn;
1668299742Sdim
1669299742Sdim      SVN_ERR(svn_fs_open_txn(&txn, fs, opt_state->txn_id, pool));
1670299742Sdim      SVN_ERR(svn_fs_change_txn_prop(txn, prop_name, prop_value, pool));
1671299742Sdim    }
1672299742Sdim  else
1673299742Sdim    SVN_ERR(svn_repos_fs_change_rev_prop4(
1674251881Speter              repos, opt_state->start_revision.value.number,
1675251881Speter              NULL, prop_name, NULL, prop_value,
1676251881Speter              opt_state->use_pre_revprop_change_hook,
1677251881Speter              opt_state->use_post_revprop_change_hook,
1678251881Speter              NULL, NULL, pool));
1679251881Speter
1680251881Speter  return SVN_NO_ERROR;
1681251881Speter}
1682251881Speter
1683251881Speter
1684251881Speter/* This implements `svn_opt_subcommand_t'. */
1685251881Speterstatic svn_error_t *
1686251881Spetersubcommand_setrevprop(apr_getopt_t *os, void *baton, apr_pool_t *pool)
1687251881Speter{
1688251881Speter  struct svnadmin_opt_state *opt_state = baton;
1689251881Speter  apr_array_header_t *args;
1690251881Speter  const char *prop_name, *filename;
1691251881Speter
1692251881Speter  /* Expect two more arguments: NAME FILE */
1693251881Speter  SVN_ERR(parse_args(&args, os, 2, 2, pool));
1694251881Speter  prop_name = APR_ARRAY_IDX(args, 0, const char *);
1695251881Speter  filename = APR_ARRAY_IDX(args, 1, const char *);
1696251881Speter  SVN_ERR(target_arg_to_dirent(&filename, filename, pool));
1697251881Speter
1698299742Sdim  if (opt_state->txn_id)
1699299742Sdim    {
1700299742Sdim      if (opt_state->start_revision.kind != svn_opt_revision_unspecified
1701299742Sdim          || opt_state->end_revision.kind != svn_opt_revision_unspecified)
1702299742Sdim        return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
1703299742Sdim                                 _("--revision (-r) and --transaction (-t) "
1704299742Sdim                                   "are mutually exclusive"));
1705299742Sdim
1706299742Sdim      if (opt_state->use_pre_revprop_change_hook
1707299742Sdim          || opt_state->use_post_revprop_change_hook)
1708299742Sdim        return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
1709299742Sdim                                 _("Calling hooks is incompatible with "
1710299742Sdim                                   "--transaction (-t)"));
1711299742Sdim    }
1712299742Sdim  else if (opt_state->start_revision.kind != svn_opt_revision_number)
1713251881Speter    return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
1714251881Speter                             _("Missing revision"));
1715251881Speter  else if (opt_state->end_revision.kind != svn_opt_revision_unspecified)
1716251881Speter    return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
1717251881Speter                             _("Only one revision allowed"));
1718251881Speter
1719251881Speter  return set_revprop(prop_name, filename, opt_state, pool);
1720251881Speter}
1721251881Speter
1722251881Speter
1723251881Speter/* This implements `svn_opt_subcommand_t'. */
1724251881Speterstatic svn_error_t *
1725251881Spetersubcommand_setuuid(apr_getopt_t *os, void *baton, apr_pool_t *pool)
1726251881Speter{
1727251881Speter  struct svnadmin_opt_state *opt_state = baton;
1728251881Speter  apr_array_header_t *args;
1729251881Speter  svn_repos_t *repos;
1730251881Speter  svn_fs_t *fs;
1731251881Speter  const char *uuid = NULL;
1732251881Speter
1733251881Speter  /* Expect zero or one more arguments: [UUID] */
1734251881Speter  SVN_ERR(parse_args(&args, os, 0, 1, pool));
1735251881Speter  if (args->nelts == 1)
1736251881Speter    uuid = APR_ARRAY_IDX(args, 0, const char *);
1737251881Speter
1738251881Speter  SVN_ERR(open_repos(&repos, opt_state->repository_path, pool));
1739251881Speter  fs = svn_repos_fs(repos);
1740251881Speter  return svn_fs_set_uuid(fs, uuid, pool);
1741251881Speter}
1742251881Speter
1743251881Speter
1744251881Speter/* This implements `svn_opt_subcommand_t'. */
1745251881Speterstatic svn_error_t *
1746251881Spetersubcommand_setlog(apr_getopt_t *os, void *baton, apr_pool_t *pool)
1747251881Speter{
1748251881Speter  struct svnadmin_opt_state *opt_state = baton;
1749251881Speter  apr_array_header_t *args;
1750251881Speter  const char *filename;
1751251881Speter
1752251881Speter  /* Expect one more argument: FILE */
1753251881Speter  SVN_ERR(parse_args(&args, os, 1, 1, pool));
1754251881Speter  filename = APR_ARRAY_IDX(args, 0, const char *);
1755251881Speter  SVN_ERR(target_arg_to_dirent(&filename, filename, pool));
1756251881Speter
1757251881Speter  if (opt_state->start_revision.kind != svn_opt_revision_number)
1758251881Speter    return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
1759251881Speter                             _("Missing revision"));
1760251881Speter  else if (opt_state->end_revision.kind != svn_opt_revision_unspecified)
1761251881Speter    return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
1762251881Speter                             _("Only one revision allowed"));
1763251881Speter
1764251881Speter  /* set_revprop() responds only to pre-/post-revprop-change opts. */
1765251881Speter  if (!opt_state->bypass_hooks)
1766251881Speter    {
1767251881Speter      opt_state->use_pre_revprop_change_hook = TRUE;
1768251881Speter      opt_state->use_post_revprop_change_hook = TRUE;
1769251881Speter    }
1770251881Speter
1771251881Speter  return set_revprop(SVN_PROP_REVISION_LOG, filename, opt_state, pool);
1772251881Speter}
1773251881Speter
1774251881Speter
1775251881Speter/* This implements 'svn_opt_subcommand_t'. */
1776251881Speterstatic svn_error_t *
1777251881Spetersubcommand_pack(apr_getopt_t *os, void *baton, apr_pool_t *pool)
1778251881Speter{
1779251881Speter  struct svnadmin_opt_state *opt_state = baton;
1780251881Speter  svn_repos_t *repos;
1781299742Sdim  svn_stream_t *feedback_stream = NULL;
1782251881Speter
1783251881Speter  /* Expect no more arguments. */
1784251881Speter  SVN_ERR(parse_args(NULL, os, 0, 0, pool));
1785251881Speter
1786251881Speter  SVN_ERR(open_repos(&repos, opt_state->repository_path, pool));
1787251881Speter
1788251881Speter  /* Progress feedback goes to STDOUT, unless they asked to suppress it. */
1789251881Speter  if (! opt_state->quiet)
1790299742Sdim    feedback_stream = recode_stream_create(stdout, pool);
1791251881Speter
1792251881Speter  return svn_error_trace(
1793251881Speter    svn_repos_fs_pack2(repos, !opt_state->quiet ? repos_notify_handler : NULL,
1794299742Sdim                       feedback_stream, check_cancel, NULL, pool));
1795251881Speter}
1796251881Speter
1797251881Speter
1798251881Speter/* This implements `svn_opt_subcommand_t'. */
1799251881Speterstatic svn_error_t *
1800251881Spetersubcommand_verify(apr_getopt_t *os, void *baton, apr_pool_t *pool)
1801251881Speter{
1802251881Speter  struct svnadmin_opt_state *opt_state = baton;
1803251881Speter  svn_repos_t *repos;
1804251881Speter  svn_fs_t *fs;
1805251881Speter  svn_revnum_t youngest, lower, upper;
1806299742Sdim  svn_stream_t *feedback_stream = NULL;
1807299742Sdim  struct repos_verify_callback_baton verify_baton = { 0 };
1808251881Speter
1809251881Speter  /* Expect no more arguments. */
1810251881Speter  SVN_ERR(parse_args(NULL, os, 0, 0, pool));
1811251881Speter
1812251881Speter  if (opt_state->txn_id
1813251881Speter      && (opt_state->start_revision.kind != svn_opt_revision_unspecified
1814251881Speter          || opt_state->end_revision.kind != svn_opt_revision_unspecified))
1815251881Speter    {
1816251881Speter      return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
1817251881Speter                               _("--revision (-r) and --transaction (-t) "
1818251881Speter                                 "are mutually exclusive"));
1819251881Speter    }
1820251881Speter
1821251881Speter  SVN_ERR(open_repos(&repos, opt_state->repository_path, pool));
1822251881Speter  fs = svn_repos_fs(repos);
1823251881Speter  SVN_ERR(svn_fs_youngest_rev(&youngest, fs, pool));
1824251881Speter
1825251881Speter  /* Usage 2. */
1826251881Speter  if (opt_state->txn_id)
1827251881Speter    {
1828251881Speter      svn_fs_txn_t *txn;
1829251881Speter      svn_fs_root_t *root;
1830251881Speter
1831251881Speter      SVN_ERR(svn_fs_open_txn(&txn, fs, opt_state->txn_id, pool));
1832251881Speter      SVN_ERR(svn_fs_txn_root(&root, txn, pool));
1833251881Speter      SVN_ERR(svn_fs_verify_root(root, pool));
1834251881Speter      return SVN_NO_ERROR;
1835251881Speter    }
1836251881Speter  else
1837251881Speter    /* Usage 1. */
1838251881Speter    ;
1839251881Speter
1840251881Speter  /* Find the revision numbers at which to start and end. */
1841251881Speter  SVN_ERR(get_revnum(&lower, &opt_state->start_revision,
1842251881Speter                     youngest, repos, pool));
1843251881Speter  SVN_ERR(get_revnum(&upper, &opt_state->end_revision,
1844251881Speter                     youngest, repos, pool));
1845251881Speter
1846251881Speter  if (upper == SVN_INVALID_REVNUM)
1847251881Speter    {
1848251881Speter      upper = lower;
1849251881Speter    }
1850251881Speter
1851299742Sdim  if (!opt_state->quiet)
1852299742Sdim    feedback_stream = recode_stream_create(stdout, pool);
1853251881Speter
1854299742Sdim  verify_baton.keep_going = opt_state->keep_going;
1855299742Sdim  verify_baton.error_summary =
1856299742Sdim    apr_array_make(pool, 0, sizeof(struct verification_error *));
1857299742Sdim  verify_baton.result_pool = pool;
1858299742Sdim
1859299742Sdim  SVN_ERR(svn_repos_verify_fs3(repos, lower, upper,
1860299742Sdim                               opt_state->check_normalization,
1861299742Sdim                               opt_state->metadata_only,
1862299742Sdim                               !opt_state->quiet
1863299742Sdim                                 ? repos_notify_handler : NULL,
1864299742Sdim                               feedback_stream,
1865299742Sdim                               repos_verify_callback, &verify_baton,
1866299742Sdim                               check_cancel, NULL, pool));
1867299742Sdim
1868299742Sdim  /* Show the --keep-going error summary. */
1869299742Sdim  if (!opt_state->quiet
1870299742Sdim      && opt_state->keep_going
1871299742Sdim      && verify_baton.error_summary->nelts > 0)
1872299742Sdim    {
1873299742Sdim      int rev_maxlength;
1874299742Sdim      svn_revnum_t end_revnum;
1875299742Sdim      apr_pool_t *iterpool;
1876299742Sdim      int i;
1877299742Sdim
1878299742Sdim      svn_error_clear(
1879299742Sdim        svn_stream_puts(feedback_stream,
1880299742Sdim                          _("\n-----Summary of corrupt revisions-----\n")));
1881299742Sdim
1882299742Sdim      /* The standard column width for the revision number is 6 characters.
1883299742Sdim         If the revision number can potentially be larger (i.e. if end_revnum
1884299742Sdim         is larger than 1000000), we increase the column width as needed. */
1885299742Sdim      rev_maxlength = 6;
1886299742Sdim      end_revnum = APR_ARRAY_IDX(verify_baton.error_summary,
1887299742Sdim                                 verify_baton.error_summary->nelts - 1,
1888299742Sdim                                 struct verification_error *)->rev;
1889299742Sdim      while (end_revnum >= 1000000)
1890299742Sdim        {
1891299742Sdim          rev_maxlength++;
1892299742Sdim          end_revnum = end_revnum / 10;
1893299742Sdim        }
1894299742Sdim
1895299742Sdim      iterpool = svn_pool_create(pool);
1896299742Sdim      for (i = 0; i < verify_baton.error_summary->nelts; i++)
1897299742Sdim        {
1898299742Sdim          struct verification_error *verr;
1899299742Sdim          svn_error_t *err;
1900299742Sdim          const char *rev_str;
1901299742Sdim
1902299742Sdim          svn_pool_clear(iterpool);
1903299742Sdim
1904299742Sdim          verr = APR_ARRAY_IDX(verify_baton.error_summary, i,
1905299742Sdim                               struct verification_error *);
1906299742Sdim
1907299742Sdim          if (verr->rev != SVN_INVALID_REVNUM)
1908299742Sdim            {
1909299742Sdim              rev_str = apr_psprintf(iterpool, "r%ld", verr->rev);
1910299742Sdim              rev_str = apr_psprintf(iterpool, "%*s", rev_maxlength, rev_str);
1911299742Sdim              for (err = svn_error_purge_tracing(verr->err);
1912299742Sdim                   err != SVN_NO_ERROR; err = err->child)
1913299742Sdim                {
1914299742Sdim                  char buf[512];
1915299742Sdim                  const char *message;
1916299742Sdim
1917299742Sdim                  message = svn_err_best_message(err, buf, sizeof(buf));
1918299742Sdim                  svn_error_clear(svn_stream_printf(feedback_stream, iterpool,
1919299742Sdim                                                    "%s: E%06d: %s\n",
1920299742Sdim                                                    rev_str, err->apr_err,
1921299742Sdim                                                    message));
1922299742Sdim                }
1923299742Sdim            }
1924299742Sdim        }
1925299742Sdim
1926299742Sdim       svn_pool_destroy(iterpool);
1927299742Sdim    }
1928299742Sdim
1929299742Sdim  if (verify_baton.error_summary->nelts > 0)
1930299742Sdim    {
1931299742Sdim      return svn_error_createf(SVN_ERR_CL_REPOS_VERIFY_FAILED, NULL,
1932299742Sdim                               _("Failed to verify repository '%s'"),
1933299742Sdim                               svn_dirent_local_style(
1934299742Sdim                                 opt_state->repository_path, pool));
1935299742Sdim    }
1936299742Sdim
1937299742Sdim  return SVN_NO_ERROR;
1938251881Speter}
1939251881Speter
1940251881Speter/* This implements `svn_opt_subcommand_t'. */
1941251881Spetersvn_error_t *
1942251881Spetersubcommand_hotcopy(apr_getopt_t *os, void *baton, apr_pool_t *pool)
1943251881Speter{
1944251881Speter  struct svnadmin_opt_state *opt_state = baton;
1945299742Sdim  svn_stream_t *feedback_stream = NULL;
1946251881Speter  apr_array_header_t *targets;
1947251881Speter  const char *new_repos_path;
1948251881Speter
1949251881Speter  /* Expect one more argument: NEW_REPOS_PATH */
1950251881Speter  SVN_ERR(parse_args(&targets, os, 1, 1, pool));
1951251881Speter  new_repos_path = APR_ARRAY_IDX(targets, 0, const char *);
1952251881Speter  SVN_ERR(target_arg_to_dirent(&new_repos_path, new_repos_path, pool));
1953251881Speter
1954299742Sdim  /* Progress feedback goes to STDOUT, unless they asked to suppress it. */
1955299742Sdim  if (! opt_state->quiet)
1956299742Sdim    feedback_stream = recode_stream_create(stdout, pool);
1957299742Sdim
1958299742Sdim  return svn_repos_hotcopy3(opt_state->repository_path, new_repos_path,
1959251881Speter                            opt_state->clean_logs, opt_state->incremental,
1960299742Sdim                            !opt_state->quiet ? repos_notify_handler : NULL,
1961299742Sdim                            feedback_stream, check_cancel, NULL, pool);
1962251881Speter}
1963251881Speter
1964299742Sdimsvn_error_t *
1965299742Sdimsubcommand_info(apr_getopt_t *os, void *baton, apr_pool_t *pool)
1966299742Sdim{
1967299742Sdim  struct svnadmin_opt_state *opt_state = baton;
1968299742Sdim  svn_repos_t *repos;
1969299742Sdim  svn_fs_t *fs;
1970299742Sdim  int fs_format;
1971299742Sdim  const char *uuid;
1972299742Sdim
1973299742Sdim  /* Expect no more arguments. */
1974299742Sdim  SVN_ERR(parse_args(NULL, os, 0, 0, pool));
1975299742Sdim
1976299742Sdim  SVN_ERR(open_repos(&repos, opt_state->repository_path, pool));
1977299742Sdim  fs = svn_repos_fs(repos);
1978299742Sdim  SVN_ERR(svn_cmdline_printf(pool, _("Path: %s\n"),
1979299742Sdim                             svn_dirent_local_style(svn_repos_path(repos, pool),
1980299742Sdim                                                    pool)));
1981299742Sdim
1982299742Sdim  SVN_ERR(svn_fs_get_uuid(fs, &uuid, pool));
1983299742Sdim  SVN_ERR(svn_cmdline_printf(pool, _("UUID: %s\n"), uuid));
1984299742Sdim  {
1985299742Sdim    int repos_format, minor;
1986299742Sdim    svn_version_t *repos_version, *fs_version;
1987299742Sdim    SVN_ERR(svn_repos_info_format(&repos_format, &repos_version,
1988299742Sdim                                  repos, pool, pool));
1989299742Sdim    SVN_ERR(svn_cmdline_printf(pool, _("Repository Format: %d\n"),
1990299742Sdim                               repos_format));
1991299742Sdim
1992299742Sdim    SVN_ERR(svn_fs_info_format(&fs_format, &fs_version,
1993299742Sdim                               fs, pool, pool));
1994299742Sdim    /* fs_format will be printed later. */
1995299742Sdim
1996299742Sdim    SVN_ERR_ASSERT(repos_version->major == SVN_VER_MAJOR);
1997299742Sdim    SVN_ERR_ASSERT(fs_version->major == SVN_VER_MAJOR);
1998299742Sdim    SVN_ERR_ASSERT(repos_version->patch == 0);
1999299742Sdim    SVN_ERR_ASSERT(fs_version->patch == 0);
2000299742Sdim
2001299742Sdim    minor = (repos_version->minor > fs_version->minor)
2002299742Sdim            ? repos_version->minor : fs_version->minor;
2003299742Sdim    SVN_ERR(svn_cmdline_printf(pool, _("Compatible With Version: %d.%d.0\n"),
2004299742Sdim                               SVN_VER_MAJOR, minor));
2005299742Sdim  }
2006299742Sdim
2007299742Sdim  {
2008299742Sdim    apr_hash_t *capabilities_set;
2009299742Sdim    apr_array_header_t *capabilities;
2010299742Sdim    int i;
2011299742Sdim
2012299742Sdim    SVN_ERR(svn_repos_capabilities(&capabilities_set, repos, pool, pool));
2013299742Sdim    capabilities = svn_sort__hash(capabilities_set,
2014299742Sdim                                  svn_sort_compare_items_lexically,
2015299742Sdim                                  pool);
2016299742Sdim
2017299742Sdim    for (i = 0; i < capabilities->nelts; i++)
2018299742Sdim      {
2019299742Sdim        svn_sort__item_t *item = &APR_ARRAY_IDX(capabilities, i,
2020299742Sdim                                                svn_sort__item_t);
2021299742Sdim        const char *capability = item->key;
2022299742Sdim        SVN_ERR(svn_cmdline_printf(pool, _("Repository Capability: %s\n"),
2023299742Sdim                                   capability));
2024299742Sdim      }
2025299742Sdim  }
2026299742Sdim
2027299742Sdim  {
2028299742Sdim    const svn_fs_info_placeholder_t *info;
2029299742Sdim
2030299742Sdim    SVN_ERR(svn_fs_info(&info, fs, pool, pool));
2031299742Sdim    SVN_ERR(svn_cmdline_printf(pool, _("Filesystem Type: %s\n"),
2032299742Sdim                               info->fs_type));
2033299742Sdim    SVN_ERR(svn_cmdline_printf(pool, _("Filesystem Format: %d\n"),
2034299742Sdim                               fs_format));
2035299742Sdim    if (!strcmp(info->fs_type, SVN_FS_TYPE_FSFS))
2036299742Sdim      {
2037299742Sdim        const svn_fs_fsfs_info_t *fsfs_info = (const void *)info;
2038299742Sdim        svn_revnum_t youngest;
2039299742Sdim        SVN_ERR(svn_fs_youngest_rev(&youngest, fs, pool));
2040299742Sdim
2041299742Sdim        if (fsfs_info->shard_size)
2042299742Sdim          SVN_ERR(svn_cmdline_printf(pool, _("FSFS Sharded: yes\n")));
2043299742Sdim        else
2044299742Sdim          SVN_ERR(svn_cmdline_printf(pool, _("FSFS Sharded: no\n")));
2045299742Sdim
2046299742Sdim        if (fsfs_info->shard_size)
2047299742Sdim          SVN_ERR(svn_cmdline_printf(pool, _("FSFS Shard Size: %d\n"),
2048299742Sdim                                     fsfs_info->shard_size));
2049299742Sdim
2050299742Sdim        /* Print packing statistics, if enabled on the FS. */
2051299742Sdim        if (fsfs_info->shard_size)
2052299742Sdim          {
2053299742Sdim            const int shard_size = fsfs_info->shard_size;
2054299742Sdim            const long shards_packed = fsfs_info->min_unpacked_rev / shard_size;
2055299742Sdim            const long shards_full = (youngest + 1) / shard_size;
2056299742Sdim            SVN_ERR(svn_cmdline_printf(pool, _("FSFS Shards Packed: %ld/%ld\n"),
2057299742Sdim                                       shards_packed, shards_full));
2058299742Sdim          }
2059299742Sdim
2060299742Sdim        if (fsfs_info->log_addressing)
2061299742Sdim          SVN_ERR(svn_cmdline_printf(pool, _("FSFS Logical Addressing: yes\n")));
2062299742Sdim        else
2063299742Sdim          SVN_ERR(svn_cmdline_printf(pool, _("FSFS Logical Addressing: no\n")));
2064299742Sdim      }
2065299742Sdim  }
2066299742Sdim
2067299742Sdim  {
2068299742Sdim    apr_array_header_t *files;
2069299742Sdim    int i;
2070299742Sdim
2071299742Sdim    SVN_ERR(svn_fs_info_config_files(&files, fs, pool, pool));
2072299742Sdim    for (i = 0; i < files->nelts; i++)
2073299742Sdim      SVN_ERR(svn_cmdline_printf(pool, _("Configuration File: %s\n"),
2074299742Sdim                                 svn_dirent_local_style(
2075299742Sdim                                   APR_ARRAY_IDX(files, i, const char *),
2076299742Sdim                                   pool)));
2077299742Sdim  }
2078299742Sdim
2079299742Sdim  /* 'svn info' prints an extra newline here, to support multiple targets.
2080299742Sdim     We'll do the same. */
2081299742Sdim  SVN_ERR(svn_cmdline_printf(pool, "\n"));
2082299742Sdim
2083299742Sdim  return SVN_NO_ERROR;
2084299742Sdim}
2085299742Sdim
2086251881Speter/* This implements `svn_opt_subcommand_t'. */
2087251881Speterstatic svn_error_t *
2088251881Spetersubcommand_lock(apr_getopt_t *os, void *baton, apr_pool_t *pool)
2089251881Speter{
2090251881Speter  struct svnadmin_opt_state *opt_state = baton;
2091251881Speter  svn_repos_t *repos;
2092251881Speter  svn_fs_t *fs;
2093251881Speter  svn_fs_access_t *access;
2094251881Speter  apr_array_header_t *args;
2095251881Speter  const char *username;
2096251881Speter  const char *lock_path;
2097251881Speter  const char *comment_file_name;
2098251881Speter  svn_stringbuf_t *file_contents;
2099251881Speter  const char *lock_path_utf8;
2100251881Speter  svn_lock_t *lock;
2101251881Speter  const char *lock_token = NULL;
2102251881Speter
2103251881Speter  /* Expect three more arguments: PATH USERNAME COMMENT-FILE */
2104251881Speter  SVN_ERR(parse_args(&args, os, 3, 4, pool));
2105251881Speter  lock_path = APR_ARRAY_IDX(args, 0, const char *);
2106251881Speter  username = APR_ARRAY_IDX(args, 1, const char *);
2107251881Speter  comment_file_name = APR_ARRAY_IDX(args, 2, const char *);
2108251881Speter
2109251881Speter  /* Expect one more optional argument: TOKEN */
2110251881Speter  if (args->nelts == 4)
2111251881Speter    lock_token = APR_ARRAY_IDX(args, 3, const char *);
2112251881Speter
2113251881Speter  SVN_ERR(target_arg_to_dirent(&comment_file_name, comment_file_name, pool));
2114251881Speter
2115251881Speter  SVN_ERR(open_repos(&repos, opt_state->repository_path, pool));
2116251881Speter  fs = svn_repos_fs(repos);
2117251881Speter
2118251881Speter  /* Create an access context describing the user. */
2119251881Speter  SVN_ERR(svn_fs_create_access(&access, username, pool));
2120251881Speter
2121251881Speter  /* Attach the access context to the filesystem. */
2122251881Speter  SVN_ERR(svn_fs_set_access(fs, access));
2123251881Speter
2124251881Speter  SVN_ERR(svn_stringbuf_from_file2(&file_contents, comment_file_name, pool));
2125251881Speter
2126251881Speter  SVN_ERR(svn_utf_cstring_to_utf8(&lock_path_utf8, lock_path, pool));
2127251881Speter
2128251881Speter  if (opt_state->bypass_hooks)
2129251881Speter    SVN_ERR(svn_fs_lock(&lock, fs, lock_path_utf8,
2130251881Speter                        lock_token,
2131251881Speter                        file_contents->data, /* comment */
2132251881Speter                        0,                   /* is_dav_comment */
2133251881Speter                        0,                   /* no expiration time. */
2134251881Speter                        SVN_INVALID_REVNUM,
2135251881Speter                        FALSE, pool));
2136251881Speter  else
2137251881Speter    SVN_ERR(svn_repos_fs_lock(&lock, repos, lock_path_utf8,
2138251881Speter                              lock_token,
2139251881Speter                              file_contents->data, /* comment */
2140251881Speter                              0,                   /* is_dav_comment */
2141251881Speter                              0,                   /* no expiration time. */
2142251881Speter                              SVN_INVALID_REVNUM,
2143251881Speter                              FALSE, pool));
2144251881Speter
2145251881Speter  SVN_ERR(svn_cmdline_printf(pool, _("'%s' locked by user '%s'.\n"),
2146251881Speter                             lock_path, username));
2147251881Speter  return SVN_NO_ERROR;
2148251881Speter}
2149251881Speter
2150251881Speterstatic svn_error_t *
2151251881Spetersubcommand_lslocks(apr_getopt_t *os, void *baton, apr_pool_t *pool)
2152251881Speter{
2153251881Speter  struct svnadmin_opt_state *opt_state = baton;
2154251881Speter  apr_array_header_t *targets;
2155251881Speter  svn_repos_t *repos;
2156251881Speter  const char *fs_path = "/";
2157251881Speter  apr_hash_t *locks;
2158251881Speter  apr_hash_index_t *hi;
2159299742Sdim  apr_pool_t *iterpool = svn_pool_create(pool);
2160251881Speter
2161251881Speter  SVN_ERR(svn_opt__args_to_target_array(&targets, os,
2162251881Speter                                        apr_array_make(pool, 0,
2163251881Speter                                                       sizeof(const char *)),
2164251881Speter                                        pool));
2165251881Speter  if (targets->nelts > 1)
2166251881Speter    return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, 0,
2167251881Speter                            _("Too many arguments given"));
2168251881Speter  if (targets->nelts)
2169251881Speter    fs_path = APR_ARRAY_IDX(targets, 0, const char *);
2170251881Speter
2171251881Speter  SVN_ERR(open_repos(&repos, opt_state->repository_path, pool));
2172251881Speter
2173251881Speter  /* Fetch all locks on or below the root directory. */
2174251881Speter  SVN_ERR(svn_repos_fs_get_locks2(&locks, repos, fs_path, svn_depth_infinity,
2175251881Speter                                  NULL, NULL, pool));
2176251881Speter
2177251881Speter  for (hi = apr_hash_first(pool, locks); hi; hi = apr_hash_next(hi))
2178251881Speter    {
2179251881Speter      const char *cr_date, *exp_date = "";
2180299742Sdim      const char *path = apr_hash_this_key(hi);
2181299742Sdim      svn_lock_t *lock = apr_hash_this_val(hi);
2182251881Speter      int comment_lines = 0;
2183251881Speter
2184299742Sdim      svn_pool_clear(iterpool);
2185251881Speter
2186299742Sdim      SVN_ERR(check_cancel(NULL));
2187299742Sdim
2188299742Sdim      cr_date = svn_time_to_human_cstring(lock->creation_date, iterpool);
2189299742Sdim
2190251881Speter      if (lock->expiration_date)
2191299742Sdim        exp_date = svn_time_to_human_cstring(lock->expiration_date, iterpool);
2192251881Speter
2193251881Speter      if (lock->comment)
2194251881Speter        comment_lines = svn_cstring_count_newlines(lock->comment) + 1;
2195251881Speter
2196299742Sdim      SVN_ERR(svn_cmdline_printf(iterpool, _("Path: %s\n"), path));
2197299742Sdim      SVN_ERR(svn_cmdline_printf(iterpool, _("UUID Token: %s\n"), lock->token));
2198299742Sdim      SVN_ERR(svn_cmdline_printf(iterpool, _("Owner: %s\n"), lock->owner));
2199299742Sdim      SVN_ERR(svn_cmdline_printf(iterpool, _("Created: %s\n"), cr_date));
2200299742Sdim      SVN_ERR(svn_cmdline_printf(iterpool, _("Expires: %s\n"), exp_date));
2201299742Sdim      SVN_ERR(svn_cmdline_printf(iterpool,
2202251881Speter                                 Q_("Comment (%i line):\n%s\n\n",
2203251881Speter                                    "Comment (%i lines):\n%s\n\n",
2204251881Speter                                    comment_lines),
2205251881Speter                                 comment_lines,
2206251881Speter                                 lock->comment ? lock->comment : ""));
2207251881Speter    }
2208251881Speter
2209299742Sdim  svn_pool_destroy(iterpool);
2210299742Sdim
2211251881Speter  return SVN_NO_ERROR;
2212251881Speter}
2213251881Speter
2214251881Speter
2215251881Speter
2216251881Speterstatic svn_error_t *
2217251881Spetersubcommand_rmlocks(apr_getopt_t *os, void *baton, apr_pool_t *pool)
2218251881Speter{
2219251881Speter  struct svnadmin_opt_state *opt_state = baton;
2220251881Speter  svn_repos_t *repos;
2221251881Speter  svn_fs_t *fs;
2222251881Speter  svn_fs_access_t *access;
2223251881Speter  svn_error_t *err;
2224251881Speter  apr_array_header_t *args;
2225251881Speter  int i;
2226251881Speter  const char *username;
2227251881Speter  apr_pool_t *subpool = svn_pool_create(pool);
2228251881Speter
2229251881Speter  SVN_ERR(open_repos(&repos, opt_state->repository_path, pool));
2230251881Speter  fs = svn_repos_fs(repos);
2231251881Speter
2232251881Speter  /* svn_fs_unlock() demands that some username be associated with the
2233251881Speter     filesystem, so just use the UID of the person running 'svnadmin'.*/
2234251881Speter  username = svn_user_get_name(pool);
2235251881Speter  if (! username)
2236251881Speter    username = "administrator";
2237251881Speter
2238251881Speter  /* Create an access context describing the current user. */
2239251881Speter  SVN_ERR(svn_fs_create_access(&access, username, pool));
2240251881Speter
2241251881Speter  /* Attach the access context to the filesystem. */
2242251881Speter  SVN_ERR(svn_fs_set_access(fs, access));
2243251881Speter
2244251881Speter  /* Parse out any options. */
2245251881Speter  SVN_ERR(svn_opt_parse_all_args(&args, os, pool));
2246251881Speter
2247251881Speter  /* Our usage requires at least one FS path. */
2248251881Speter  if (args->nelts == 0)
2249251881Speter    return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, 0,
2250251881Speter                            _("No paths to unlock provided"));
2251251881Speter
2252251881Speter  /* All the rest of the arguments are paths from which to remove locks. */
2253251881Speter  for (i = 0; i < args->nelts; i++)
2254251881Speter    {
2255251881Speter      const char *lock_path = APR_ARRAY_IDX(args, i, const char *);
2256251881Speter      const char *lock_path_utf8;
2257251881Speter      svn_lock_t *lock;
2258251881Speter
2259251881Speter      SVN_ERR(svn_utf_cstring_to_utf8(&lock_path_utf8, lock_path, subpool));
2260251881Speter
2261251881Speter      /* Fetch the path's svn_lock_t. */
2262251881Speter      err = svn_fs_get_lock(&lock, fs, lock_path_utf8, subpool);
2263251881Speter      if (err)
2264251881Speter        goto move_on;
2265251881Speter      if (! lock)
2266251881Speter        {
2267251881Speter          SVN_ERR(svn_cmdline_printf(subpool,
2268251881Speter                                     _("Path '%s' isn't locked.\n"),
2269251881Speter                                     lock_path));
2270251881Speter          continue;
2271251881Speter        }
2272251881Speter
2273251881Speter      /* Now forcibly destroy the lock. */
2274251881Speter      err = svn_fs_unlock(fs, lock_path_utf8,
2275251881Speter                          lock->token, 1 /* force */, subpool);
2276251881Speter      if (err)
2277251881Speter        goto move_on;
2278251881Speter
2279251881Speter      SVN_ERR(svn_cmdline_printf(subpool,
2280251881Speter                                 _("Removed lock on '%s'.\n"), lock->path));
2281251881Speter
2282251881Speter    move_on:
2283251881Speter      if (err)
2284251881Speter        {
2285251881Speter          /* Print the error, but move on to the next lock. */
2286251881Speter          svn_handle_error2(err, stderr, FALSE /* non-fatal */, "svnadmin: ");
2287251881Speter          svn_error_clear(err);
2288251881Speter        }
2289251881Speter
2290251881Speter      svn_pool_clear(subpool);
2291251881Speter    }
2292251881Speter
2293251881Speter  svn_pool_destroy(subpool);
2294251881Speter  return SVN_NO_ERROR;
2295251881Speter}
2296251881Speter
2297251881Speter
2298251881Speter/* This implements `svn_opt_subcommand_t'. */
2299251881Speterstatic svn_error_t *
2300251881Spetersubcommand_unlock(apr_getopt_t *os, void *baton, apr_pool_t *pool)
2301251881Speter{
2302251881Speter  struct svnadmin_opt_state *opt_state = baton;
2303251881Speter  svn_repos_t *repos;
2304251881Speter  svn_fs_t *fs;
2305251881Speter  svn_fs_access_t *access;
2306251881Speter  apr_array_header_t *args;
2307251881Speter  const char *username;
2308251881Speter  const char *lock_path;
2309251881Speter  const char *lock_path_utf8;
2310251881Speter  const char *lock_token = NULL;
2311251881Speter
2312251881Speter  /* Expect three more arguments: PATH USERNAME TOKEN */
2313251881Speter  SVN_ERR(parse_args(&args, os, 3, 3, pool));
2314251881Speter  lock_path = APR_ARRAY_IDX(args, 0, const char *);
2315251881Speter  username = APR_ARRAY_IDX(args, 1, const char *);
2316251881Speter  lock_token = APR_ARRAY_IDX(args, 2, const char *);
2317251881Speter
2318251881Speter  /* Open the repos/FS, and associate an access context containing
2319251881Speter     USERNAME. */
2320251881Speter  SVN_ERR(open_repos(&repos, opt_state->repository_path, pool));
2321251881Speter  fs = svn_repos_fs(repos);
2322251881Speter  SVN_ERR(svn_fs_create_access(&access, username, pool));
2323251881Speter  SVN_ERR(svn_fs_set_access(fs, access));
2324251881Speter
2325251881Speter  SVN_ERR(svn_utf_cstring_to_utf8(&lock_path_utf8, lock_path, pool));
2326251881Speter  if (opt_state->bypass_hooks)
2327251881Speter    SVN_ERR(svn_fs_unlock(fs, lock_path_utf8, lock_token,
2328251881Speter                          FALSE, pool));
2329251881Speter  else
2330251881Speter    SVN_ERR(svn_repos_fs_unlock(repos, lock_path_utf8, lock_token,
2331251881Speter                                FALSE, pool));
2332251881Speter
2333251881Speter  SVN_ERR(svn_cmdline_printf(pool, _("'%s' unlocked by user '%s'.\n"),
2334251881Speter                             lock_path, username));
2335251881Speter  return SVN_NO_ERROR;
2336251881Speter}
2337251881Speter
2338251881Speter
2339251881Speter/* This implements `svn_opt_subcommand_t'. */
2340251881Speterstatic svn_error_t *
2341251881Spetersubcommand_upgrade(apr_getopt_t *os, void *baton, apr_pool_t *pool)
2342251881Speter{
2343251881Speter  svn_error_t *err;
2344251881Speter  struct svnadmin_opt_state *opt_state = baton;
2345299742Sdim  svn_stream_t *feedback_stream = NULL;
2346251881Speter
2347251881Speter  /* Expect no more arguments. */
2348251881Speter  SVN_ERR(parse_args(NULL, os, 0, 0, pool));
2349251881Speter
2350299742Sdim  SVN_ERR(svn_stream_for_stdout(&feedback_stream, pool));
2351251881Speter
2352251881Speter  /* Restore default signal handlers. */
2353251881Speter  setup_cancellation_signals(SIG_DFL);
2354251881Speter
2355251881Speter  err = svn_repos_upgrade2(opt_state->repository_path, TRUE,
2356299742Sdim                           repos_notify_handler, feedback_stream, pool);
2357251881Speter  if (err)
2358251881Speter    {
2359251881Speter      if (APR_STATUS_IS_EAGAIN(err->apr_err))
2360251881Speter        {
2361251881Speter          svn_error_clear(err);
2362251881Speter          err = SVN_NO_ERROR;
2363251881Speter          if (! opt_state->wait)
2364251881Speter            return svn_error_create(SVN_ERR_REPOS_LOCKED, NULL,
2365251881Speter                                    _("Failed to get exclusive repository "
2366251881Speter                                      "access; perhaps another process\n"
2367251881Speter                                      "such as httpd, svnserve or svn "
2368251881Speter                                      "has it open?"));
2369251881Speter          SVN_ERR(svn_cmdline_printf(pool,
2370251881Speter                                     _("Waiting on repository lock; perhaps"
2371251881Speter                                       " another process has it open?\n")));
2372251881Speter          SVN_ERR(svn_cmdline_fflush(stdout));
2373251881Speter          SVN_ERR(svn_repos_upgrade2(opt_state->repository_path, FALSE,
2374299742Sdim                                     repos_notify_handler, feedback_stream,
2375251881Speter                                     pool));
2376251881Speter        }
2377251881Speter      else if (err->apr_err == SVN_ERR_FS_UNSUPPORTED_UPGRADE)
2378251881Speter        {
2379251881Speter          return svn_error_quick_wrap(err,
2380251881Speter                    _("Upgrade of this repository's underlying versioned "
2381251881Speter                    "filesystem is not supported; consider "
2382251881Speter                    "dumping and loading the data elsewhere"));
2383251881Speter        }
2384251881Speter      else if (err->apr_err == SVN_ERR_REPOS_UNSUPPORTED_UPGRADE)
2385251881Speter        {
2386251881Speter          return svn_error_quick_wrap(err,
2387251881Speter                    _("Upgrade of this repository is not supported; consider "
2388251881Speter                    "dumping and loading the data elsewhere"));
2389251881Speter        }
2390251881Speter    }
2391251881Speter  SVN_ERR(err);
2392251881Speter
2393251881Speter  SVN_ERR(svn_cmdline_printf(pool, _("\nUpgrade completed.\n")));
2394251881Speter  return SVN_NO_ERROR;
2395251881Speter}
2396251881Speter
2397251881Speter
2398299742Sdim/* This implements `svn_opt_subcommand_t'. */
2399299742Sdimstatic svn_error_t *
2400299742Sdimsubcommand_delrevprop(apr_getopt_t *os, void *baton, apr_pool_t *pool)
2401299742Sdim{
2402299742Sdim  struct svnadmin_opt_state *opt_state = baton;
2403299742Sdim  apr_array_header_t *args;
2404299742Sdim  const char *prop_name;
2405299742Sdim
2406299742Sdim  /* Expect one more argument: NAME */
2407299742Sdim  SVN_ERR(parse_args(&args, os, 1, 1, pool));
2408299742Sdim  prop_name = APR_ARRAY_IDX(args, 0, const char *);
2409299742Sdim
2410299742Sdim  if (opt_state->txn_id)
2411299742Sdim    {
2412299742Sdim      if (opt_state->start_revision.kind != svn_opt_revision_unspecified
2413299742Sdim          || opt_state->end_revision.kind != svn_opt_revision_unspecified)
2414299742Sdim        return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
2415299742Sdim                                 _("--revision (-r) and --transaction (-t) "
2416299742Sdim                                   "are mutually exclusive"));
2417299742Sdim
2418299742Sdim      if (opt_state->use_pre_revprop_change_hook
2419299742Sdim          || opt_state->use_post_revprop_change_hook)
2420299742Sdim        return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
2421299742Sdim                                 _("Calling hooks is incompatible with "
2422299742Sdim                                   "--transaction (-t)"));
2423299742Sdim    }
2424299742Sdim  else if (opt_state->start_revision.kind != svn_opt_revision_number)
2425299742Sdim    return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
2426299742Sdim                             _("Missing revision"));
2427299742Sdim  else if (opt_state->end_revision.kind != svn_opt_revision_unspecified)
2428299742Sdim    return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
2429299742Sdim                             _("Only one revision allowed"));
2430299742Sdim
2431299742Sdim  return set_revprop(prop_name, NULL, opt_state, pool);
2432299742Sdim}
2433299742Sdim
2434299742Sdim
2435251881Speter
2436251881Speter/** Main. **/
2437251881Speter
2438299742Sdim/*
2439299742Sdim * On success, leave *EXIT_CODE untouched and return SVN_NO_ERROR. On error,
2440299742Sdim * either return an error to be displayed, or set *EXIT_CODE to non-zero and
2441299742Sdim * return SVN_NO_ERROR.
2442299742Sdim */
2443299742Sdimstatic svn_error_t *
2444299742Sdimsub_main(int *exit_code, int argc, const char *argv[], apr_pool_t *pool)
2445251881Speter{
2446251881Speter  svn_error_t *err;
2447251881Speter  apr_status_t apr_err;
2448251881Speter
2449251881Speter  const svn_opt_subcommand_desc2_t *subcommand = NULL;
2450251881Speter  struct svnadmin_opt_state opt_state = { 0 };
2451251881Speter  apr_getopt_t *os;
2452251881Speter  int opt_id;
2453251881Speter  apr_array_header_t *received_opts;
2454251881Speter  int i;
2455251881Speter  svn_boolean_t dash_F_arg = FALSE;
2456251881Speter
2457251881Speter  received_opts = apr_array_make(pool, SVN_OPT_MAX_OPTIONS, sizeof(int));
2458251881Speter
2459251881Speter  /* Check library versions */
2460299742Sdim  SVN_ERR(check_lib_versions());
2461251881Speter
2462251881Speter  /* Initialize the FS library. */
2463299742Sdim  SVN_ERR(svn_fs_initialize(pool));
2464251881Speter
2465251881Speter  if (argc <= 1)
2466251881Speter    {
2467299742Sdim      SVN_ERR(subcommand_help(NULL, NULL, pool));
2468299742Sdim      *exit_code = EXIT_FAILURE;
2469299742Sdim      return SVN_NO_ERROR;
2470251881Speter    }
2471251881Speter
2472251881Speter  /* Initialize opt_state. */
2473251881Speter  opt_state.start_revision.kind = svn_opt_revision_unspecified;
2474251881Speter  opt_state.end_revision.kind = svn_opt_revision_unspecified;
2475251881Speter  opt_state.memory_cache_size = svn_cache_config_get()->cache_size;
2476251881Speter
2477251881Speter  /* Parse options. */
2478299742Sdim  SVN_ERR(svn_cmdline__getopt_init(&os, argc, argv, pool));
2479251881Speter
2480251881Speter  os->interleave = 1;
2481251881Speter
2482251881Speter  while (1)
2483251881Speter    {
2484251881Speter      const char *opt_arg;
2485251881Speter      const char *utf8_opt_arg;
2486251881Speter
2487251881Speter      /* Parse the next option. */
2488251881Speter      apr_err = apr_getopt_long(os, options_table, &opt_id, &opt_arg);
2489251881Speter      if (APR_STATUS_IS_EOF(apr_err))
2490251881Speter        break;
2491251881Speter      else if (apr_err)
2492251881Speter        {
2493299742Sdim          SVN_ERR(subcommand_help(NULL, NULL, pool));
2494299742Sdim          *exit_code = EXIT_FAILURE;
2495299742Sdim          return SVN_NO_ERROR;
2496251881Speter        }
2497251881Speter
2498251881Speter      /* Stash the option code in an array before parsing it. */
2499251881Speter      APR_ARRAY_PUSH(received_opts, int) = opt_id;
2500251881Speter
2501251881Speter      switch (opt_id) {
2502251881Speter      case 'r':
2503251881Speter        {
2504251881Speter          if (opt_state.start_revision.kind != svn_opt_revision_unspecified)
2505251881Speter            {
2506299742Sdim              return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
2507299742Sdim                        _("Multiple revision arguments encountered; "
2508299742Sdim                          "try '-r N:M' instead of '-r N -r M'"));
2509251881Speter            }
2510251881Speter          if (svn_opt_parse_revision(&(opt_state.start_revision),
2511251881Speter                                     &(opt_state.end_revision),
2512251881Speter                                     opt_arg, pool) != 0)
2513251881Speter            {
2514299742Sdim              SVN_ERR(svn_utf_cstring_to_utf8(&utf8_opt_arg, opt_arg, pool));
2515251881Speter
2516299742Sdim              return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
2517251881Speter                        _("Syntax error in revision argument '%s'"),
2518251881Speter                        utf8_opt_arg);
2519251881Speter            }
2520251881Speter        }
2521251881Speter        break;
2522251881Speter      case 't':
2523251881Speter        opt_state.txn_id = opt_arg;
2524251881Speter        break;
2525251881Speter
2526251881Speter      case 'q':
2527251881Speter        opt_state.quiet = TRUE;
2528251881Speter        break;
2529251881Speter      case 'h':
2530251881Speter      case '?':
2531251881Speter        opt_state.help = TRUE;
2532251881Speter        break;
2533251881Speter      case 'M':
2534251881Speter        opt_state.memory_cache_size
2535251881Speter            = 0x100000 * apr_strtoi64(opt_arg, NULL, 0);
2536251881Speter        break;
2537251881Speter      case 'F':
2538299742Sdim        SVN_ERR(svn_utf_cstring_to_utf8(&utf8_opt_arg, opt_arg, pool));
2539299742Sdim        SVN_ERR(svn_stringbuf_from_file2(&(opt_state.filedata),
2540251881Speter                                             utf8_opt_arg, pool));
2541251881Speter        dash_F_arg = TRUE;
2542251881Speter      case svnadmin__version:
2543251881Speter        opt_state.version = TRUE;
2544251881Speter        break;
2545251881Speter      case svnadmin__incremental:
2546251881Speter        opt_state.incremental = TRUE;
2547251881Speter        break;
2548251881Speter      case svnadmin__deltas:
2549251881Speter        opt_state.use_deltas = TRUE;
2550251881Speter        break;
2551251881Speter      case svnadmin__ignore_uuid:
2552251881Speter        opt_state.uuid_action = svn_repos_load_uuid_ignore;
2553251881Speter        break;
2554251881Speter      case svnadmin__force_uuid:
2555251881Speter        opt_state.uuid_action = svn_repos_load_uuid_force;
2556251881Speter        break;
2557251881Speter      case svnadmin__pre_1_4_compatible:
2558299742Sdim        opt_state.compatible_version = apr_pcalloc(pool, sizeof(svn_version_t));
2559299742Sdim        opt_state.compatible_version->major = 1;
2560299742Sdim        opt_state.compatible_version->minor = 3;
2561251881Speter        break;
2562251881Speter      case svnadmin__pre_1_5_compatible:
2563299742Sdim        opt_state.compatible_version = apr_pcalloc(pool, sizeof(svn_version_t));
2564299742Sdim        opt_state.compatible_version->major = 1;
2565299742Sdim        opt_state.compatible_version->minor = 4;
2566251881Speter        break;
2567251881Speter      case svnadmin__pre_1_6_compatible:
2568299742Sdim        opt_state.compatible_version = apr_pcalloc(pool, sizeof(svn_version_t));
2569299742Sdim        opt_state.compatible_version->major = 1;
2570299742Sdim        opt_state.compatible_version->minor = 5;
2571251881Speter        break;
2572251881Speter      case svnadmin__compatible_version:
2573251881Speter        {
2574251881Speter          svn_version_t latest = { SVN_VER_MAJOR, SVN_VER_MINOR,
2575251881Speter                                   SVN_VER_PATCH, NULL };
2576251881Speter          svn_version_t *compatible_version;
2577251881Speter
2578251881Speter          /* Parse the version string which carries our target
2579251881Speter             compatibility. */
2580299742Sdim          SVN_ERR(svn_version__parse_version_string(&compatible_version,
2581251881Speter                                                        opt_arg, pool));
2582251881Speter
2583251881Speter          /* We can't create repository with a version older than 1.0.0.  */
2584251881Speter          if (! svn_version__at_least(compatible_version, 1, 0, 0))
2585251881Speter            {
2586299742Sdim              return svn_error_createf(SVN_ERR_UNSUPPORTED_FEATURE, NULL,
2587299742Sdim                                       _("Cannot create pre-1.0-compatible "
2588299742Sdim                                         "repositories"));
2589251881Speter            }
2590251881Speter
2591251881Speter          /* We can't create repository with a version newer than what
2592251881Speter             the running version of Subversion supports. */
2593251881Speter          if (! svn_version__at_least(&latest,
2594251881Speter                                      compatible_version->major,
2595251881Speter                                      compatible_version->minor,
2596251881Speter                                      compatible_version->patch))
2597251881Speter            {
2598299742Sdim              return svn_error_createf(SVN_ERR_UNSUPPORTED_FEATURE, NULL,
2599299742Sdim                                       _("Cannot guarantee compatibility "
2600299742Sdim                                         "beyond the current running version "
2601299742Sdim                                         "(%s)"),
2602299742Sdim                                       SVN_VER_NUM);
2603251881Speter            }
2604251881Speter
2605251881Speter          opt_state.compatible_version = compatible_version;
2606251881Speter        }
2607251881Speter        break;
2608299742Sdim      case svnadmin__keep_going:
2609299742Sdim        opt_state.keep_going = TRUE;
2610299742Sdim        break;
2611299742Sdim      case svnadmin__check_normalization:
2612299742Sdim        opt_state.check_normalization = TRUE;
2613299742Sdim        break;
2614299742Sdim      case svnadmin__metadata_only:
2615299742Sdim        opt_state.metadata_only = TRUE;
2616299742Sdim        break;
2617251881Speter      case svnadmin__fs_type:
2618299742Sdim        SVN_ERR(svn_utf_cstring_to_utf8(&opt_state.fs_type, opt_arg, pool));
2619251881Speter        break;
2620251881Speter      case svnadmin__parent_dir:
2621299742Sdim        SVN_ERR(svn_utf_cstring_to_utf8(&opt_state.parent_dir, opt_arg,
2622251881Speter                                            pool));
2623251881Speter        opt_state.parent_dir
2624251881Speter          = svn_dirent_internal_style(opt_state.parent_dir, pool);
2625251881Speter        break;
2626251881Speter      case svnadmin__use_pre_commit_hook:
2627251881Speter        opt_state.use_pre_commit_hook = TRUE;
2628251881Speter        break;
2629251881Speter      case svnadmin__use_post_commit_hook:
2630251881Speter        opt_state.use_post_commit_hook = TRUE;
2631251881Speter        break;
2632251881Speter      case svnadmin__use_pre_revprop_change_hook:
2633251881Speter        opt_state.use_pre_revprop_change_hook = TRUE;
2634251881Speter        break;
2635251881Speter      case svnadmin__use_post_revprop_change_hook:
2636251881Speter        opt_state.use_post_revprop_change_hook = TRUE;
2637251881Speter        break;
2638251881Speter      case svnadmin__bdb_txn_nosync:
2639251881Speter        opt_state.bdb_txn_nosync = TRUE;
2640251881Speter        break;
2641251881Speter      case svnadmin__bdb_log_keep:
2642251881Speter        opt_state.bdb_log_keep = TRUE;
2643251881Speter        break;
2644251881Speter      case svnadmin__bypass_hooks:
2645251881Speter        opt_state.bypass_hooks = TRUE;
2646251881Speter        break;
2647251881Speter      case svnadmin__bypass_prop_validation:
2648251881Speter        opt_state.bypass_prop_validation = TRUE;
2649251881Speter        break;
2650299742Sdim      case svnadmin__ignore_dates:
2651299742Sdim        opt_state.ignore_dates = TRUE;
2652299742Sdim        break;
2653251881Speter      case svnadmin__clean_logs:
2654251881Speter        opt_state.clean_logs = TRUE;
2655251881Speter        break;
2656251881Speter      case svnadmin__config_dir:
2657299742Sdim        SVN_ERR(svn_utf_cstring_to_utf8(&utf8_opt_arg, opt_arg, pool));
2658251881Speter        opt_state.config_dir =
2659251881Speter            apr_pstrdup(pool, svn_dirent_canonicalize(utf8_opt_arg, pool));
2660251881Speter        break;
2661251881Speter      case svnadmin__wait:
2662251881Speter        opt_state.wait = TRUE;
2663251881Speter        break;
2664251881Speter      default:
2665251881Speter        {
2666299742Sdim          SVN_ERR(subcommand_help(NULL, NULL, pool));
2667299742Sdim          *exit_code = EXIT_FAILURE;
2668299742Sdim          return SVN_NO_ERROR;
2669251881Speter        }
2670251881Speter      }  /* close `switch' */
2671251881Speter    }  /* close `while' */
2672251881Speter
2673251881Speter  /* If the user asked for help, then the rest of the arguments are
2674251881Speter     the names of subcommands to get help on (if any), or else they're
2675251881Speter     just typos/mistakes.  Whatever the case, the subcommand to
2676251881Speter     actually run is subcommand_help(). */
2677251881Speter  if (opt_state.help)
2678251881Speter    subcommand = svn_opt_get_canonical_subcommand2(cmd_table, "help");
2679251881Speter
2680251881Speter  /* If we're not running the `help' subcommand, then look for a
2681251881Speter     subcommand in the first argument. */
2682251881Speter  if (subcommand == NULL)
2683251881Speter    {
2684251881Speter      if (os->ind >= os->argc)
2685251881Speter        {
2686251881Speter          if (opt_state.version)
2687251881Speter            {
2688251881Speter              /* Use the "help" subcommand to handle the "--version" option. */
2689251881Speter              static const svn_opt_subcommand_desc2_t pseudo_cmd =
2690251881Speter                { "--version", subcommand_help, {0}, "",
2691251881Speter                  {svnadmin__version,  /* must accept its own option */
2692251881Speter                   'q',  /* --quiet */
2693251881Speter                  } };
2694251881Speter
2695251881Speter              subcommand = &pseudo_cmd;
2696251881Speter            }
2697251881Speter          else
2698251881Speter            {
2699251881Speter              svn_error_clear(svn_cmdline_fprintf(stderr, pool,
2700251881Speter                                        _("subcommand argument required\n")));
2701299742Sdim              SVN_ERR(subcommand_help(NULL, NULL, pool));
2702299742Sdim              *exit_code = EXIT_FAILURE;
2703299742Sdim              return SVN_NO_ERROR;
2704251881Speter            }
2705251881Speter        }
2706251881Speter      else
2707251881Speter        {
2708251881Speter          const char *first_arg = os->argv[os->ind++];
2709251881Speter          subcommand = svn_opt_get_canonical_subcommand2(cmd_table, first_arg);
2710251881Speter          if (subcommand == NULL)
2711251881Speter            {
2712251881Speter              const char *first_arg_utf8;
2713299742Sdim              SVN_ERR(svn_utf_cstring_to_utf8(&first_arg_utf8,
2714251881Speter                                                  first_arg, pool));
2715251881Speter              svn_error_clear(
2716251881Speter                svn_cmdline_fprintf(stderr, pool,
2717251881Speter                                    _("Unknown subcommand: '%s'\n"),
2718251881Speter                                    first_arg_utf8));
2719299742Sdim              SVN_ERR(subcommand_help(NULL, NULL, pool));
2720299742Sdim              *exit_code = EXIT_FAILURE;
2721299742Sdim              return SVN_NO_ERROR;
2722251881Speter            }
2723251881Speter        }
2724251881Speter    }
2725251881Speter
2726251881Speter  /* Every subcommand except `help' and `freeze' with '-F' require a
2727251881Speter     second argument -- the repository path.  Parse it out here and
2728251881Speter     store it in opt_state. */
2729251881Speter  if (!(subcommand->cmd_func == subcommand_help
2730251881Speter        || (subcommand->cmd_func == subcommand_freeze && dash_F_arg)))
2731251881Speter    {
2732251881Speter      const char *repos_path = NULL;
2733251881Speter
2734251881Speter      if (os->ind >= os->argc)
2735251881Speter        {
2736299742Sdim          return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
2737299742Sdim                                  _("Repository argument required"));
2738251881Speter        }
2739251881Speter
2740299742Sdim      SVN_ERR(svn_utf_cstring_to_utf8(&repos_path, os->argv[os->ind++], pool));
2741251881Speter
2742251881Speter      if (svn_path_is_url(repos_path))
2743251881Speter        {
2744299742Sdim          return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
2745299742Sdim                                   _("'%s' is a URL when it should be a "
2746299742Sdim                                     "local path"), repos_path);
2747251881Speter        }
2748251881Speter
2749251881Speter      opt_state.repository_path = svn_dirent_internal_style(repos_path, pool);
2750251881Speter    }
2751251881Speter
2752251881Speter  /* Check that the subcommand wasn't passed any inappropriate options. */
2753251881Speter  for (i = 0; i < received_opts->nelts; i++)
2754251881Speter    {
2755251881Speter      opt_id = APR_ARRAY_IDX(received_opts, i, int);
2756251881Speter
2757251881Speter      /* All commands implicitly accept --help, so just skip over this
2758251881Speter         when we see it. Note that we don't want to include this option
2759251881Speter         in their "accepted options" list because it would be awfully
2760251881Speter         redundant to display it in every commands' help text. */
2761251881Speter      if (opt_id == 'h' || opt_id == '?')
2762251881Speter        continue;
2763251881Speter
2764251881Speter      if (! svn_opt_subcommand_takes_option3(subcommand, opt_id, NULL))
2765251881Speter        {
2766251881Speter          const char *optstr;
2767251881Speter          const apr_getopt_option_t *badopt =
2768251881Speter            svn_opt_get_option_from_code2(opt_id, options_table, subcommand,
2769251881Speter                                          pool);
2770251881Speter          svn_opt_format_option(&optstr, badopt, FALSE, pool);
2771251881Speter          if (subcommand->name[0] == '-')
2772299742Sdim            SVN_ERR(subcommand_help(NULL, NULL, pool));
2773251881Speter          else
2774251881Speter            svn_error_clear(svn_cmdline_fprintf(stderr, pool
2775251881Speter                            , _("Subcommand '%s' doesn't accept option '%s'\n"
2776251881Speter                                "Type 'svnadmin help %s' for usage.\n"),
2777251881Speter                subcommand->name, optstr, subcommand->name));
2778299742Sdim          *exit_code = EXIT_FAILURE;
2779299742Sdim          return SVN_NO_ERROR;
2780251881Speter        }
2781251881Speter    }
2782251881Speter
2783251881Speter  /* Set up our cancellation support. */
2784251881Speter  setup_cancellation_signals(signal_handler);
2785251881Speter
2786251881Speter#ifdef SIGPIPE
2787251881Speter  /* Disable SIGPIPE generation for the platforms that have it. */
2788251881Speter  apr_signal(SIGPIPE, SIG_IGN);
2789251881Speter#endif
2790251881Speter
2791251881Speter#ifdef SIGXFSZ
2792251881Speter  /* Disable SIGXFSZ generation for the platforms that have it, otherwise
2793251881Speter   * working with large files when compiled against an APR that doesn't have
2794251881Speter   * large file support will crash the program, which is uncool. */
2795251881Speter  apr_signal(SIGXFSZ, SIG_IGN);
2796251881Speter#endif
2797251881Speter
2798251881Speter  /* Configure FSFS caches for maximum efficiency with svnadmin.
2799251881Speter   * Also, apply the respective command line parameters, if given. */
2800251881Speter  {
2801251881Speter    svn_cache_config_t settings = *svn_cache_config_get();
2802251881Speter
2803251881Speter    settings.cache_size = opt_state.memory_cache_size;
2804251881Speter    settings.single_threaded = TRUE;
2805251881Speter
2806251881Speter    svn_cache_config_set(&settings);
2807251881Speter  }
2808251881Speter
2809251881Speter  /* Run the subcommand. */
2810251881Speter  err = (*subcommand->cmd_func)(os, &opt_state, pool);
2811251881Speter  if (err)
2812251881Speter    {
2813251881Speter      /* For argument-related problems, suggest using the 'help'
2814251881Speter         subcommand. */
2815251881Speter      if (err->apr_err == SVN_ERR_CL_INSUFFICIENT_ARGS
2816251881Speter          || err->apr_err == SVN_ERR_CL_ARG_PARSING_ERROR)
2817251881Speter        {
2818251881Speter          err = svn_error_quick_wrap(err,
2819251881Speter                                     _("Try 'svnadmin help' for more info"));
2820251881Speter        }
2821299742Sdim      return err;
2822251881Speter    }
2823299742Sdim
2824299742Sdim  return SVN_NO_ERROR;
2825251881Speter}
2826251881Speter
2827251881Speterint
2828251881Spetermain(int argc, const char *argv[])
2829251881Speter{
2830251881Speter  apr_pool_t *pool;
2831299742Sdim  int exit_code = EXIT_SUCCESS;
2832299742Sdim  svn_error_t *err;
2833251881Speter
2834251881Speter  /* Initialize the app. */
2835251881Speter  if (svn_cmdline_init("svnadmin", stderr) != EXIT_SUCCESS)
2836251881Speter    return EXIT_FAILURE;
2837251881Speter
2838251881Speter  /* Create our top-level pool.  Use a separate mutexless allocator,
2839251881Speter   * given this application is single threaded.
2840251881Speter   */
2841251881Speter  pool = apr_allocator_owner_get(svn_pool_create_allocator(FALSE));
2842251881Speter
2843299742Sdim  err = sub_main(&exit_code, argc, argv, pool);
2844251881Speter
2845299742Sdim  /* Flush stdout and report if it fails. It would be flushed on exit anyway
2846299742Sdim     but this makes sure that output is not silently lost if it fails. */
2847299742Sdim  err = svn_error_compose_create(err, svn_cmdline_fflush(stdout));
2848299742Sdim
2849299742Sdim  if (err)
2850299742Sdim    {
2851299742Sdim      exit_code = EXIT_FAILURE;
2852299742Sdim      svn_cmdline_handle_exit_error(err, NULL, "svnadmin: ");
2853299742Sdim    }
2854299742Sdim
2855251881Speter  svn_pool_destroy(pool);
2856251881Speter  return exit_code;
2857251881Speter}
2858