]> CyberLeo.Net >> Repos - FreeBSD/FreeBSD.git/blob - contrib/subversion/subversion/svnadmin/svnadmin.c
Update svn-1.9.7 to 1.10.0.
[FreeBSD/FreeBSD.git] / contrib / subversion / subversion / svnadmin / svnadmin.c
1 /*
2  * svnadmin.c: Subversion server administration tool main file.
3  *
4  * ====================================================================
5  *    Licensed to the Apache Software Foundation (ASF) under one
6  *    or more contributor license agreements.  See the NOTICE file
7  *    distributed with this work for additional information
8  *    regarding copyright ownership.  The ASF licenses this file
9  *    to you under the Apache License, Version 2.0 (the
10  *    "License"); you may not use this file except in compliance
11  *    with the License.  You may obtain a copy of the License at
12  *
13  *      http://www.apache.org/licenses/LICENSE-2.0
14  *
15  *    Unless required by applicable law or agreed to in writing,
16  *    software distributed under the License is distributed on an
17  *    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
18  *    KIND, either express or implied.  See the License for the
19  *    specific language governing permissions and limitations
20  *    under the License.
21  * ====================================================================
22  */
23
24
25 #include <apr_file_io.h>
26
27 #include "svn_hash.h"
28 #include "svn_pools.h"
29 #include "svn_cmdline.h"
30 #include "svn_error.h"
31 #include "svn_opt.h"
32 #include "svn_utf.h"
33 #include "svn_subst.h"
34 #include "svn_dirent_uri.h"
35 #include "svn_path.h"
36 #include "svn_config.h"
37 #include "svn_repos.h"
38 #include "svn_cache_config.h"
39 #include "svn_version.h"
40 #include "svn_props.h"
41 #include "svn_sorts.h"
42 #include "svn_time.h"
43 #include "svn_user.h"
44 #include "svn_xml.h"
45
46 #include "private/svn_cmdline_private.h"
47 #include "private/svn_opt_private.h"
48 #include "private/svn_sorts_private.h"
49 #include "private/svn_subr_private.h"
50 #include "private/svn_cmdline_private.h"
51 #include "private/svn_fspath.h"
52
53 #include "svn_private_config.h"
54
55 \f
56 /*** Code. ***/
57
58 /* FSFS format 7's "block-read" feature performs poorly with small caches.
59  * Enable it only if caches above this threshold have been configured.
60  * The current threshold is 64MB. */
61 #define BLOCK_READ_CACHE_THRESHOLD (0x40 * 0x100000)
62
63 static svn_cancel_func_t check_cancel = NULL;
64
65 /* Custom filesystem warning function. */
66 static void
67 warning_func(void *baton,
68              svn_error_t *err)
69 {
70   if (! err)
71     return;
72   svn_handle_warning2(stderr, err, "svnadmin: ");
73 }
74
75
76 /* Version compatibility check */
77 static svn_error_t *
78 check_lib_versions(void)
79 {
80   static const svn_version_checklist_t checklist[] =
81     {
82       { "svn_subr",  svn_subr_version },
83       { "svn_repos", svn_repos_version },
84       { "svn_fs",    svn_fs_version },
85       { "svn_delta", svn_delta_version },
86       { NULL, NULL }
87     };
88   SVN_VERSION_DEFINE(my_version);
89
90   return svn_ver_check_list2(&my_version, checklist, svn_ver_equal);
91 }
92
93
94 \f
95 /** Subcommands. **/
96
97 static svn_opt_subcommand_t
98   subcommand_crashtest,
99   subcommand_create,
100   subcommand_delrevprop,
101   subcommand_deltify,
102   subcommand_dump,
103   subcommand_dump_revprops,
104   subcommand_freeze,
105   subcommand_help,
106   subcommand_hotcopy,
107   subcommand_info,
108   subcommand_load,
109   subcommand_load_revprops,
110   subcommand_list_dblogs,
111   subcommand_list_unused_dblogs,
112   subcommand_lock,
113   subcommand_lslocks,
114   subcommand_lstxns,
115   subcommand_pack,
116   subcommand_recover,
117   subcommand_rmlocks,
118   subcommand_rmtxns,
119   subcommand_setlog,
120   subcommand_setrevprop,
121   subcommand_setuuid,
122   subcommand_unlock,
123   subcommand_upgrade,
124   subcommand_verify;
125
126 enum svnadmin__cmdline_options_t
127   {
128     svnadmin__version = SVN_OPT_FIRST_LONGOPT_ID,
129     svnadmin__incremental,
130     svnadmin__keep_going,
131     svnadmin__deltas,
132     svnadmin__ignore_uuid,
133     svnadmin__force_uuid,
134     svnadmin__fs_type,
135     svnadmin__parent_dir,
136     svnadmin__bdb_txn_nosync,
137     svnadmin__bdb_log_keep,
138     svnadmin__config_dir,
139     svnadmin__bypass_hooks,
140     svnadmin__bypass_prop_validation,
141     svnadmin__ignore_dates,
142     svnadmin__use_pre_commit_hook,
143     svnadmin__use_post_commit_hook,
144     svnadmin__use_pre_revprop_change_hook,
145     svnadmin__use_post_revprop_change_hook,
146     svnadmin__clean_logs,
147     svnadmin__wait,
148     svnadmin__pre_1_4_compatible,
149     svnadmin__pre_1_5_compatible,
150     svnadmin__pre_1_6_compatible,
151     svnadmin__compatible_version,
152     svnadmin__check_normalization,
153     svnadmin__metadata_only,
154     svnadmin__no_flush_to_disk,
155     svnadmin__normalize_props,
156     svnadmin__exclude,
157     svnadmin__include,
158     svnadmin__glob
159   };
160
161 /* Option codes and descriptions.
162  *
163  * The entire list must be terminated with an entry of nulls.
164  */
165 static const apr_getopt_option_t options_table[] =
166   {
167     {"help",          'h', 0,
168      N_("show help on a subcommand")},
169
170     {NULL,            '?', 0,
171      N_("show help on a subcommand")},
172
173     {"version",       svnadmin__version, 0,
174      N_("show program version information")},
175
176     {"revision",      'r', 1,
177      N_("specify revision number ARG (or X:Y range)")},
178
179     {"transaction",       't', 1,
180      N_("specify transaction name ARG")},
181
182     {"incremental",   svnadmin__incremental, 0,
183      N_("dump or hotcopy incrementally")},
184
185     {"deltas",        svnadmin__deltas, 0,
186      N_("use deltas in dump output")},
187
188     {"bypass-hooks",  svnadmin__bypass_hooks, 0,
189      N_("bypass the repository hook system")},
190
191     {"bypass-prop-validation",  svnadmin__bypass_prop_validation, 0,
192      N_("bypass property validation logic")},
193
194     {"ignore-dates",  svnadmin__ignore_dates, 0,
195      N_("ignore revision datestamps found in the stream")},
196
197     {"quiet",         'q', 0,
198      N_("no progress (only errors to stderr)")},
199
200     {"ignore-uuid",   svnadmin__ignore_uuid, 0,
201      N_("ignore any repos UUID found in the stream")},
202
203     {"force-uuid",    svnadmin__force_uuid, 0,
204      N_("set repos UUID to that found in stream, if any")},
205
206     {"fs-type",       svnadmin__fs_type, 1,
207      N_("type of repository:\n"
208         "                             'fsfs' (default), 'bdb' or 'fsx'\n"
209         "                             CAUTION: FSX is for EXPERIMENTAL use only!")},
210
211     {"parent-dir",    svnadmin__parent_dir, 1,
212      N_("load at specified directory in repository")},
213
214     {"bdb-txn-nosync", svnadmin__bdb_txn_nosync, 0,
215      N_("disable fsync at transaction commit [Berkeley DB]")},
216
217     {"bdb-log-keep",  svnadmin__bdb_log_keep, 0,
218      N_("disable automatic log file removal [Berkeley DB]")},
219
220     {"config-dir",    svnadmin__config_dir, 1,
221      N_("read user configuration files from directory ARG")},
222
223     {"clean-logs",    svnadmin__clean_logs, 0,
224      N_("remove redundant Berkeley DB log files\n"
225         "                             from source repository [Berkeley DB]")},
226
227     {"use-pre-commit-hook", svnadmin__use_pre_commit_hook, 0,
228      N_("call pre-commit hook before committing revisions")},
229
230     {"use-post-commit-hook", svnadmin__use_post_commit_hook, 0,
231      N_("call post-commit hook after committing revisions")},
232
233     {"use-pre-revprop-change-hook", svnadmin__use_pre_revprop_change_hook, 0,
234      N_("call hook before changing revision property")},
235
236     {"use-post-revprop-change-hook", svnadmin__use_post_revprop_change_hook, 0,
237      N_("call hook after changing revision property")},
238
239     {"wait",          svnadmin__wait, 0,
240      N_("wait instead of exit if the repository is in\n"
241         "                             use by another process")},
242
243     {"pre-1.4-compatible",     svnadmin__pre_1_4_compatible, 0,
244      N_("deprecated; see --compatible-version")},
245
246     {"pre-1.5-compatible",     svnadmin__pre_1_5_compatible, 0,
247      N_("deprecated; see --compatible-version")},
248
249     {"pre-1.6-compatible",     svnadmin__pre_1_6_compatible, 0,
250      N_("deprecated; see --compatible-version")},
251
252     {"keep-going",    svnadmin__keep_going, 0,
253      N_("continue verification after detecting a corruption")},
254
255     {"memory-cache-size",     'M', 1,
256      N_("size of the extra in-memory cache in MB used to\n"
257         "                             minimize redundant operations. Default: 16.\n"
258         "                             [used for FSFS repositories only]")},
259
260     {"compatible-version",     svnadmin__compatible_version, 1,
261      N_("use repository format compatible with Subversion\n"
262         "                             version ARG (\"1.5.5\", \"1.7\", etc.)")},
263
264     {"file", 'F', 1, N_("read repository paths from file ARG")},
265
266     {"check-normalization", svnadmin__check_normalization, 0,
267      N_("report any names within the same directory or\n"
268         "                             svn:mergeinfo property value that differ only\n"
269         "                             in character representation, but are otherwise\n"
270         "                             identical")},
271
272     {"metadata-only", svnadmin__metadata_only, 0,
273      N_("verify metadata only (ignored for BDB),\n"
274         "                             checking against external corruption in\n"
275         "                             Subversion 1.9+ format repositories.\n")},
276
277     {"no-flush-to-disk", svnadmin__no_flush_to_disk, 0,
278      N_("disable flushing to disk during the operation\n"
279         "                             (faster, but unsafe on power off)")},
280
281     {"normalize-props", svnadmin__normalize_props, 0,
282      N_("normalize property values found in the dumpstream\n"
283         "                             (currently, only translates non-LF line endings)")},
284
285     {"exclude", svnadmin__exclude, 1,
286      N_("filter out nodes with given prefix(es) from dump")},
287
288     {"include", svnadmin__include, 1,
289      N_("filter out nodes without given prefix(es) from dump")},
290
291     {"pattern", svnadmin__glob, 0,
292      N_("treat the path prefixes as file glob patterns.\n"
293         "                             Glob special characters are '*' '?' '[]' and '\\'.\n"
294         "                             Character '/' is not treated specially, so\n"
295         "                             pattern /*/foo matches paths /a/foo and /a/b/foo.") },
296
297     {NULL}
298   };
299
300
301 /* Array of available subcommands.
302  * The entire list must be terminated with an entry of nulls.
303  */
304 static const svn_opt_subcommand_desc2_t cmd_table[] =
305 {
306   {"crashtest", subcommand_crashtest, {0}, N_
307    ("usage: svnadmin crashtest REPOS_PATH\n\n"
308     "Open the repository at REPOS_PATH, then abort, thus simulating\n"
309     "a process that crashes while holding an open repository handle.\n"),
310    {0} },
311
312   {"create", subcommand_create, {0}, N_
313    ("usage: svnadmin create REPOS_PATH\n\n"
314     "Create a new, empty repository at REPOS_PATH.\n"),
315    {svnadmin__bdb_txn_nosync, svnadmin__bdb_log_keep,
316     svnadmin__config_dir, svnadmin__fs_type, svnadmin__compatible_version,
317     svnadmin__pre_1_4_compatible, svnadmin__pre_1_5_compatible,
318     svnadmin__pre_1_6_compatible
319     } },
320
321   {"delrevprop", subcommand_delrevprop, {0}, N_
322    ("usage: 1. svnadmin delrevprop REPOS_PATH -r REVISION NAME\n"
323     "                   2. svnadmin delrevprop REPOS_PATH -t TXN NAME\n\n"
324     "1. Delete the property NAME on revision REVISION.\n\n"
325     "Use --use-pre-revprop-change-hook/--use-post-revprop-change-hook to\n"
326     "trigger the revision property-related hooks (for example, if you want\n"
327     "an email notification sent from your post-revprop-change hook).\n\n"
328     "NOTE: Revision properties are not versioned, so this command will\n"
329     "irreversibly destroy the previous value of the property.\n\n"
330     "2. Delete the property NAME on transaction TXN.\n"),
331    {'r', 't', svnadmin__use_pre_revprop_change_hook,
332     svnadmin__use_post_revprop_change_hook} },
333
334   {"deltify", subcommand_deltify, {0}, N_
335    ("usage: svnadmin deltify [-r LOWER[:UPPER]] REPOS_PATH\n\n"
336     "Run over the requested revision range, performing predecessor delti-\n"
337     "fication on the paths changed in those revisions.  Deltification in\n"
338     "essence compresses the repository by only storing the differences or\n"
339     "delta from the preceding revision.  If no revisions are specified,\n"
340     "this will simply deltify the HEAD revision.\n"),
341    {'r', 'q', 'M'} },
342
343   {"dump", subcommand_dump, {0}, N_
344    ("usage: svnadmin dump REPOS_PATH [-r LOWER[:UPPER] [--incremental]]\n\n"
345     "Dump the contents of filesystem to stdout in a 'dumpfile'\n"
346     "portable format, sending feedback to stderr.  Dump revisions\n"
347     "LOWER rev through UPPER rev.  If no revisions are given, dump all\n"
348     "revision trees.  If only LOWER is given, dump that one revision tree.\n"
349     "If --incremental is passed, the first revision dumped will describe\n"
350     "only the paths changed in that revision; otherwise it will describe\n"
351     "every path present in the repository as of that revision.  (In either\n"
352     "case, the second and subsequent revisions, if any, describe only paths\n"
353     "changed in those revisions.)\n"
354     "\n"
355     "Using --exclude or --include gives results equivalent to authz-based\n"
356     "path exclusions. In particular, when the source of a copy is\n"
357     "excluded, the copy is transformed into an add (unlike in 'svndumpfilter').\n"),
358   {'r', svnadmin__incremental, svnadmin__deltas, 'q', 'M', 'F',
359    svnadmin__exclude, svnadmin__include, svnadmin__glob },
360   {{'F', N_("write to file ARG instead of stdout")}} },
361
362   {"dump-revprops", subcommand_dump_revprops, {0}, N_
363    ("usage: svnadmin dump-revprops REPOS_PATH [-r LOWER[:UPPER]]\n\n"
364     "Dump the revision properties of filesystem to stdout in a 'dumpfile'\n"
365     "portable format, sending feedback to stderr.  Dump revisions\n"
366     "LOWER rev through UPPER rev.  If no revisions are given, dump the\n"
367     "properties for all revisions.  If only LOWER is given, dump the\n"
368     "properties for that one revision.\n"),
369   {'r', 'q', 'F'},
370   {{'F', N_("write to file ARG instead of stdout")}} },
371
372   {"freeze", subcommand_freeze, {0}, N_
373    ("usage: 1. svnadmin freeze REPOS_PATH PROGRAM [ARG...]\n"
374     "               2. svnadmin freeze -F FILE PROGRAM [ARG...]\n\n"
375     "1. Run PROGRAM passing ARGS while holding a write-lock on REPOS_PATH.\n"
376     "   Allows safe use of third-party backup tools on a live repository.\n"
377     "\n"
378     "2. Like 1 except all repositories listed in FILE are locked. The file\n"
379     "   format is repository paths separated by newlines.  Repositories are\n"
380     "   locked in the same order as they are listed in the file.\n"),
381    {'F'},
382    {{'F', N_("read repository paths from file ARG")}} },
383
384   {"help", subcommand_help, {"?", "h"}, N_
385    ("usage: svnadmin help [SUBCOMMAND...]\n\n"
386     "Describe the usage of this program or its subcommands.\n"),
387    {0} },
388
389   {"hotcopy", subcommand_hotcopy, {0}, N_
390    ("usage: svnadmin hotcopy REPOS_PATH NEW_REPOS_PATH\n\n"
391     "Make a hot copy of a repository.\n"
392     "If --incremental is passed, data which already exists at the destination\n"
393     "is not copied again.  Incremental mode is implemented for FSFS repositories.\n"),
394    {svnadmin__clean_logs, svnadmin__incremental, 'q'} },
395
396   {"info", subcommand_info, {0}, N_
397    ("usage: svnadmin info REPOS_PATH\n\n"
398     "Print information about the repository at REPOS_PATH.\n"),
399    {0} },
400
401   {"list-dblogs", subcommand_list_dblogs, {0}, N_
402    ("usage: svnadmin list-dblogs REPOS_PATH\n\n"
403     "List all Berkeley DB log files.\n\n"
404     "WARNING: Modifying or deleting logfiles which are still in use\n"
405     "will cause your repository to be corrupted.\n"),
406    {0} },
407
408   {"list-unused-dblogs", subcommand_list_unused_dblogs, {0}, N_
409    ("usage: svnadmin list-unused-dblogs REPOS_PATH\n\n"
410     "List unused Berkeley DB log files.\n\n"),
411    {0} },
412
413   {"load", subcommand_load, {0}, N_
414    ("usage: svnadmin load REPOS_PATH\n\n"
415     "Read a 'dumpfile'-formatted stream from stdin, committing\n"
416     "new revisions into the repository's filesystem.  If the repository\n"
417     "was previously empty, its UUID will, by default, be changed to the\n"
418     "one specified in the stream.  Progress feedback is sent to stdout.\n"
419     "If --revision is specified, limit the loaded revisions to only those\n"
420     "in the dump stream whose revision numbers match the specified range.\n"),
421    {'q', 'r', svnadmin__ignore_uuid, svnadmin__force_uuid,
422     svnadmin__ignore_dates,
423     svnadmin__use_pre_commit_hook, svnadmin__use_post_commit_hook,
424     svnadmin__parent_dir, svnadmin__normalize_props,
425     svnadmin__bypass_prop_validation, 'M',
426     svnadmin__no_flush_to_disk, 'F'},
427    {{'F', N_("read from file ARG instead of stdin")}} },
428
429   {"load-revprops", subcommand_load_revprops, {0}, N_
430    ("usage: svnadmin load-revprops REPOS_PATH\n\n"
431     "Read a 'dumpfile'-formatted stream from stdin, setting the revision\n"
432     "properties in the repository's filesystem.  Revisions not found in the\n"
433     "repository will cause an error.  Progress feedback is sent to stdout.\n"
434     "If --revision is specified, limit the loaded revisions to only those\n"
435     "in the dump stream whose revision numbers match the specified range.\n"),
436    {'q', 'r', svnadmin__force_uuid, svnadmin__normalize_props,
437     svnadmin__bypass_prop_validation, svnadmin__no_flush_to_disk, 'F'},
438    {{'F', N_("read from file ARG instead of stdin")}} },
439
440   {"lock", subcommand_lock, {0}, N_
441    ("usage: svnadmin lock REPOS_PATH PATH USERNAME COMMENT-FILE [TOKEN]\n\n"
442     "Lock PATH by USERNAME setting comments from COMMENT-FILE.\n"
443     "If provided, use TOKEN as lock token.  Use --bypass-hooks to avoid\n"
444     "triggering the pre-lock and post-lock hook scripts.\n"),
445   {svnadmin__bypass_hooks, 'q'} },
446
447   {"lslocks", subcommand_lslocks, {0}, N_
448    ("usage: svnadmin lslocks REPOS_PATH [PATH-IN-REPOS]\n\n"
449     "Print descriptions of all locks on or under PATH-IN-REPOS (which,\n"
450     "if not provided, is the root of the repository).\n"),
451    {0} },
452
453   {"lstxns", subcommand_lstxns, {0}, N_
454    ("usage: svnadmin lstxns REPOS_PATH\n\n"
455     "Print the names of uncommitted transactions. With -rN skip the output\n"
456     "of those that have a base revision more recent than rN.  Transactions\n"
457     "with base revisions much older than HEAD are likely to have been\n"
458     "abandonded and are candidates to be removed.\n"),
459    {'r'},
460    { {'r', "transaction base revision ARG"} } },
461
462   {"pack", subcommand_pack, {0}, N_
463    ("usage: svnadmin pack REPOS_PATH\n\n"
464     "Possibly compact the repository into a more efficient storage model.\n"
465     "This may not apply to all repositories, in which case, exit.\n"),
466    {'q', 'M'} },
467
468   {"recover", subcommand_recover, {0}, N_
469    ("usage: svnadmin recover REPOS_PATH\n\n"
470     "Run the recovery procedure on a repository.  Do this if you've\n"
471     "been getting errors indicating that recovery ought to be run.\n"
472     "Berkeley DB recovery requires exclusive access and will\n"
473     "exit if the repository is in use by another process.\n"),
474    {svnadmin__wait} },
475
476   {"rmlocks", subcommand_rmlocks, {0}, N_
477    ("usage: svnadmin rmlocks REPOS_PATH LOCKED_PATH...\n\n"
478     "Unconditionally remove lock from each LOCKED_PATH.\n"),
479    {'q'} },
480
481   {"rmtxns", subcommand_rmtxns, {0}, N_
482    ("usage: svnadmin rmtxns REPOS_PATH TXN_NAME...\n\n"
483     "Delete the named transaction(s).\n"),
484    {'q'} },
485
486   {"setlog", subcommand_setlog, {0}, N_
487    ("usage: svnadmin setlog REPOS_PATH -r REVISION FILE\n\n"
488     "Set the log-message on revision REVISION to the contents of FILE.  Use\n"
489     "--bypass-hooks to avoid triggering the revision-property-related hooks\n"
490     "(for example, if you do not want an email notification sent\n"
491     "from your post-revprop-change hook, or because the modification of\n"
492     "revision properties has not been enabled in the pre-revprop-change\n"
493     "hook).\n\n"
494     "NOTE: Revision properties are not versioned, so this command will\n"
495     "overwrite the previous log message.\n"),
496    {'r', svnadmin__bypass_hooks} },
497
498   {"setrevprop", subcommand_setrevprop, {0}, N_
499    ("usage: 1. svnadmin setrevprop REPOS_PATH -r REVISION NAME FILE\n"
500     "                   2. svnadmin setrevprop REPOS_PATH -t TXN NAME FILE\n\n"
501     "1. Set the property NAME on revision REVISION to the contents of FILE.\n\n"
502     "Use --use-pre-revprop-change-hook/--use-post-revprop-change-hook to\n"
503     "trigger the revision property-related hooks (for example, if you want\n"
504     "an email notification sent from your post-revprop-change hook).\n\n"
505     "NOTE: Revision properties are not versioned, so this command will\n"
506     "overwrite the previous value of the property.\n\n"
507     "2. Set the property NAME on transaction TXN to the contents of FILE.\n"),
508    {'r', 't', svnadmin__use_pre_revprop_change_hook,
509     svnadmin__use_post_revprop_change_hook} },
510
511   {"setuuid", subcommand_setuuid, {0}, N_
512    ("usage: svnadmin setuuid REPOS_PATH [NEW_UUID]\n\n"
513     "Reset the repository UUID for the repository located at REPOS_PATH.  If\n"
514     "NEW_UUID is provided, use that as the new repository UUID; otherwise,\n"
515     "generate a brand new UUID for the repository.\n"),
516    {0} },
517
518   {"unlock", subcommand_unlock, {0}, N_
519    ("usage: svnadmin unlock REPOS_PATH LOCKED_PATH USERNAME TOKEN\n\n"
520     "Unlock LOCKED_PATH (as USERNAME) after verifying that the token\n"
521     "associated with the lock matches TOKEN.  Use --bypass-hooks to avoid\n"
522     "triggering the pre-unlock and post-unlock hook scripts.\n"),
523    {svnadmin__bypass_hooks, 'q'} },
524
525   {"upgrade", subcommand_upgrade, {0}, N_
526    ("usage: svnadmin upgrade REPOS_PATH\n\n"
527     "Upgrade the repository located at REPOS_PATH to the latest supported\n"
528     "schema version.\n\n"
529     "This functionality is provided as a convenience for repository\n"
530     "administrators who wish to make use of new Subversion functionality\n"
531     "without having to undertake a potentially costly full repository dump\n"
532     "and load operation.  As such, the upgrade performs only the minimum\n"
533     "amount of work needed to accomplish this while still maintaining the\n"
534     "integrity of the repository.  It does not guarantee the most optimized\n"
535     "repository state as a dump and subsequent load would.\n"),
536    {0} },
537
538   {"verify", subcommand_verify, {0}, N_
539    ("usage: svnadmin verify REPOS_PATH\n\n"
540     "Verify the data stored in the repository.\n"),
541    {'t', 'r', 'q', svnadmin__keep_going, 'M',
542     svnadmin__check_normalization, svnadmin__metadata_only} },
543
544   { NULL, NULL, {0}, NULL, {0} }
545 };
546
547
548 /* Baton for passing option/argument state to a subcommand function. */
549 struct svnadmin_opt_state
550 {
551   const char *repository_path;
552   const char *fs_type;                              /* --fs-type */
553   svn_version_t *compatible_version;                /* --compatible-version */
554   svn_opt_revision_t start_revision, end_revision;  /* -r X[:Y] */
555   const char *txn_id;                               /* -t TXN */
556   svn_boolean_t help;                               /* --help or -? */
557   svn_boolean_t version;                            /* --version */
558   svn_boolean_t incremental;                        /* --incremental */
559   svn_boolean_t use_deltas;                         /* --deltas */
560   svn_boolean_t use_pre_commit_hook;                /* --use-pre-commit-hook */
561   svn_boolean_t use_post_commit_hook;               /* --use-post-commit-hook */
562   svn_boolean_t use_pre_revprop_change_hook;        /* --use-pre-revprop-change-hook */
563   svn_boolean_t use_post_revprop_change_hook;       /* --use-post-revprop-change-hook */
564   svn_boolean_t quiet;                              /* --quiet */
565   svn_boolean_t bdb_txn_nosync;                     /* --bdb-txn-nosync */
566   svn_boolean_t bdb_log_keep;                       /* --bdb-log-keep */
567   svn_boolean_t clean_logs;                         /* --clean-logs */
568   svn_boolean_t bypass_hooks;                       /* --bypass-hooks */
569   svn_boolean_t wait;                               /* --wait */
570   svn_boolean_t keep_going;                         /* --keep-going */
571   svn_boolean_t check_normalization;                /* --check-normalization */
572   svn_boolean_t metadata_only;                      /* --metadata-only */
573   svn_boolean_t bypass_prop_validation;             /* --bypass-prop-validation */
574   svn_boolean_t ignore_dates;                       /* --ignore-dates */
575   svn_boolean_t no_flush_to_disk;                   /* --no-flush-to-disk */
576   svn_boolean_t normalize_props;                    /* --normalize_props */
577   enum svn_repos_load_uuid uuid_action;             /* --ignore-uuid,
578                                                        --force-uuid */
579   apr_uint64_t memory_cache_size;                   /* --memory-cache-size M */
580   const char *parent_dir;                           /* --parent-dir */
581   const char *file;                                 /* --file */
582   apr_array_header_t *exclude;                      /* --exclude */
583   apr_array_header_t *include;                      /* --include */
584   svn_boolean_t glob;                               /* --pattern */
585
586   const char *config_dir;    /* Overriding Configuration Directory */
587 };
588
589
590 /* Helper to open a repository and set a warning func (so we don't
591  * SEGFAULT when libsvn_fs's default handler gets run).  */
592 static svn_error_t *
593 open_repos(svn_repos_t **repos,
594            const char *path,
595            struct svnadmin_opt_state *opt_state,
596            apr_pool_t *pool)
597 {
598   /* Enable the "block-read" feature (where it applies)? */
599   svn_boolean_t use_block_read
600     = svn_cache_config_get()->cache_size > BLOCK_READ_CACHE_THRESHOLD;
601
602   /* construct FS configuration parameters: enable caches for r/o data */
603   apr_hash_t *fs_config = apr_hash_make(pool);
604   svn_hash_sets(fs_config, SVN_FS_CONFIG_FSFS_CACHE_DELTAS, "1");
605   svn_hash_sets(fs_config, SVN_FS_CONFIG_FSFS_CACHE_FULLTEXTS, "1");
606   svn_hash_sets(fs_config, SVN_FS_CONFIG_FSFS_CACHE_NODEPROPS, "1");
607   svn_hash_sets(fs_config, SVN_FS_CONFIG_FSFS_CACHE_REVPROPS, "2");
608   svn_hash_sets(fs_config, SVN_FS_CONFIG_FSFS_CACHE_NS,
609                            svn_uuid_generate(pool));
610   svn_hash_sets(fs_config, SVN_FS_CONFIG_FSFS_BLOCK_READ,
611                            use_block_read ? "1" : "0");
612   svn_hash_sets(fs_config, SVN_FS_CONFIG_NO_FLUSH_TO_DISK,
613                            opt_state->no_flush_to_disk ? "1" : "0");
614
615   /* now, open the requested repository */
616   SVN_ERR(svn_repos_open3(repos, path, fs_config, pool, pool));
617   svn_fs_set_warning_func(svn_repos_fs(*repos), warning_func, NULL);
618   return SVN_NO_ERROR;
619 }
620
621
622 /* Set *REVNUM to the revision specified by REVISION (or to
623    SVN_INVALID_REVNUM if that has the type 'unspecified'),
624    possibly making use of the YOUNGEST revision number in REPOS. */
625 static svn_error_t *
626 get_revnum(svn_revnum_t *revnum, const svn_opt_revision_t *revision,
627            svn_revnum_t youngest, svn_repos_t *repos, apr_pool_t *pool)
628 {
629   if (revision->kind == svn_opt_revision_number)
630     *revnum = revision->value.number;
631   else if (revision->kind == svn_opt_revision_head)
632     *revnum = youngest;
633   else if (revision->kind == svn_opt_revision_date)
634     SVN_ERR(svn_repos_dated_revision(revnum, repos, revision->value.date,
635                                      pool));
636   else if (revision->kind == svn_opt_revision_unspecified)
637     *revnum = SVN_INVALID_REVNUM;
638   else
639     return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
640                             _("Invalid revision specifier"));
641
642   if (*revnum > youngest)
643     return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
644        _("Revisions must not be greater than the youngest revision (%ld)"),
645        youngest);
646
647   return SVN_NO_ERROR;
648 }
649
650 /* Set *FSPATH to an internal-style fspath parsed from ARG. */
651 static svn_error_t *
652 target_arg_to_fspath(const char **fspath,
653                      const char *arg,
654                      apr_pool_t *result_pool,
655                      apr_pool_t *scratch_pool)
656 {
657   /* ### Using a private API.  This really shouldn't be needed. */
658   *fspath = svn_fspath__canonicalize(arg, result_pool);
659   return SVN_NO_ERROR;
660 }
661
662 /* Set *DIRENT to an internal-style, local dirent path
663    allocated from POOL and parsed from PATH. */
664 static svn_error_t *
665 target_arg_to_dirent(const char **dirent,
666                      const char *path,
667                      apr_pool_t *pool)
668 {
669   if (svn_path_is_url(path))
670     return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
671                              _("Path '%s' is not a local path"), path);
672   *dirent = svn_dirent_internal_style(path, pool);
673   return SVN_NO_ERROR;
674 }
675
676 /* Parse the remaining command-line arguments from OS, returning them
677    in a new array *ARGS (allocated from POOL) and optionally verifying
678    that we got the expected number thereof.  If MIN_EXPECTED is not
679    negative, return an error if the function would return fewer than
680    MIN_EXPECTED arguments.  If MAX_EXPECTED is not negative, return an
681    error if the function would return more than MAX_EXPECTED
682    arguments.
683
684    As a special case, when MIN_EXPECTED and MAX_EXPECTED are both 0,
685    allow ARGS to be NULL.  */
686 static svn_error_t *
687 parse_args(apr_array_header_t **args,
688            apr_getopt_t *os,
689            int min_expected,
690            int max_expected,
691            apr_pool_t *pool)
692 {
693   int num_args = os ? (os->argc - os->ind) : 0;
694
695   if (min_expected || max_expected)
696     SVN_ERR_ASSERT(args);
697
698   if ((min_expected >= 0) && (num_args < min_expected))
699     return svn_error_create(SVN_ERR_CL_INSUFFICIENT_ARGS, 0,
700                             _("Not enough arguments"));
701   if ((max_expected >= 0) && (num_args > max_expected))
702     return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, 0,
703                             _("Too many arguments"));
704   if (args)
705     {
706       *args = apr_array_make(pool, num_args, sizeof(const char *));
707
708       if (num_args)
709         while (os->ind < os->argc)
710           {
711             const char *arg;
712
713             SVN_ERR(svn_utf_cstring_to_utf8(&arg, os->argv[os->ind++], pool));
714             APR_ARRAY_PUSH(*args, const char *) = arg;
715           }
716     }
717
718   return SVN_NO_ERROR;
719 }
720
721
722 /* This implements 'svn_error_malfunction_handler_t. */
723 static svn_error_t *
724 crashtest_malfunction_handler(svn_boolean_t can_return,
725                               const char *file,
726                               int line,
727                               const char *expr)
728 {
729   abort();
730   return SVN_NO_ERROR; /* Not reached. */
731 }
732
733 /* This implements `svn_opt_subcommand_t'. */
734 static svn_error_t *
735 subcommand_crashtest(apr_getopt_t *os, void *baton, apr_pool_t *pool)
736 {
737   struct svnadmin_opt_state *opt_state = baton;
738   svn_repos_t *repos;
739
740   (void)svn_error_set_malfunction_handler(crashtest_malfunction_handler);
741   SVN_ERR(open_repos(&repos, opt_state->repository_path, opt_state, pool));
742   SVN_ERR(svn_cmdline_printf(pool,
743                              _("Successfully opened repository '%s'.\n"
744                                "Will now crash to simulate a crashing "
745                                "server process.\n"),
746                              svn_dirent_local_style(opt_state->repository_path,
747                                                     pool)));
748   SVN_ERR_MALFUNCTION();
749
750   /* merely silence a compiler warning (this will never be executed) */
751   return SVN_NO_ERROR;
752 }
753
754 /* This implements `svn_opt_subcommand_t'. */
755 static svn_error_t *
756 subcommand_create(apr_getopt_t *os, void *baton, apr_pool_t *pool)
757 {
758   struct svnadmin_opt_state *opt_state = baton;
759   svn_repos_t *repos;
760   apr_hash_t *fs_config = apr_hash_make(pool);
761
762   /* Expect no more arguments. */
763   SVN_ERR(parse_args(NULL, os, 0, 0, pool));
764
765   svn_hash_sets(fs_config, SVN_FS_CONFIG_BDB_TXN_NOSYNC,
766                 (opt_state->bdb_txn_nosync ? "1" :"0"));
767
768   svn_hash_sets(fs_config, SVN_FS_CONFIG_BDB_LOG_AUTOREMOVE,
769                 (opt_state->bdb_log_keep ? "0" :"1"));
770
771   if (opt_state->fs_type)
772     {
773       /* With 1.8 we are announcing that BDB is deprecated.  No support
774        * has been removed and it will continue to work until some future
775        * date.  The purpose here is to discourage people from creating
776        * new BDB repositories which they will need to dump/load into
777        * FSFS or some new FS type in the future. */
778       if (0 == strcmp(opt_state->fs_type, SVN_FS_TYPE_BDB))
779         {
780           SVN_ERR(svn_cmdline_fprintf(
781                       stderr, pool,
782                       _("%swarning:"
783                         " The \"%s\" repository back-end is deprecated,"
784                         " consider using \"%s\" instead.\n"),
785                       "svnadmin: ", SVN_FS_TYPE_BDB, SVN_FS_TYPE_FSFS));
786           fflush(stderr);
787         }
788       svn_hash_sets(fs_config, SVN_FS_CONFIG_FS_TYPE, opt_state->fs_type);
789     }
790
791   if (opt_state->compatible_version)
792     {
793       if (! svn_version__at_least(opt_state->compatible_version, 1, 4, 0))
794         svn_hash_sets(fs_config, SVN_FS_CONFIG_PRE_1_4_COMPATIBLE, "1");
795       if (! svn_version__at_least(opt_state->compatible_version, 1, 5, 0))
796         svn_hash_sets(fs_config, SVN_FS_CONFIG_PRE_1_5_COMPATIBLE, "1");
797       if (! svn_version__at_least(opt_state->compatible_version, 1, 6, 0))
798         svn_hash_sets(fs_config, SVN_FS_CONFIG_PRE_1_6_COMPATIBLE, "1");
799       if (! svn_version__at_least(opt_state->compatible_version, 1, 8, 0))
800         svn_hash_sets(fs_config, SVN_FS_CONFIG_PRE_1_8_COMPATIBLE, "1");
801       /* In 1.9, we figured out that we didn't have to keep extending this
802          madness indefinitely. */
803       svn_hash_sets(fs_config, SVN_FS_CONFIG_COMPATIBLE_VERSION,
804                     apr_psprintf(pool, "%d.%d.%d%s%s",
805                                  opt_state->compatible_version->major,
806                                  opt_state->compatible_version->minor,
807                                  opt_state->compatible_version->patch,
808                                  opt_state->compatible_version->tag
809                                  ? "-" : "",
810                                  opt_state->compatible_version->tag
811                                  ? opt_state->compatible_version->tag : ""));
812     }
813
814   if (opt_state->compatible_version)
815     {
816       if (! svn_version__at_least(opt_state->compatible_version, 1, 1, 0)
817           /* ### TODO: this NULL check hard-codes knowledge of the library's
818                        default fs-type value */
819           && (opt_state->fs_type == NULL
820               || !strcmp(opt_state->fs_type, SVN_FS_TYPE_FSFS)))
821         {
822           return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
823                                   _("Repositories compatible with 1.0.x must "
824                                     "use --fs-type=bdb"));
825         }
826
827       if (! svn_version__at_least(opt_state->compatible_version, 1, 9, 0)
828           && opt_state->fs_type && !strcmp(opt_state->fs_type, SVN_FS_TYPE_FSX))
829         {
830           return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
831                                    _("Repositories compatible with 1.8.x or "
832                                      "earlier cannot use --fs-type=%s"),
833                                    SVN_FS_TYPE_FSX);
834         }
835     }
836
837   SVN_ERR(svn_repos_create(&repos, opt_state->repository_path,
838                            NULL, NULL, NULL, fs_config, pool));
839   svn_fs_set_warning_func(svn_repos_fs(repos), warning_func, NULL);
840   return SVN_NO_ERROR;
841 }
842
843
844 /* This implements `svn_opt_subcommand_t'. */
845 static svn_error_t *
846 subcommand_deltify(apr_getopt_t *os, void *baton, apr_pool_t *pool)
847 {
848   struct svnadmin_opt_state *opt_state = baton;
849   svn_repos_t *repos;
850   svn_fs_t *fs;
851   svn_revnum_t start = SVN_INVALID_REVNUM, end = SVN_INVALID_REVNUM;
852   svn_revnum_t youngest, revision;
853   apr_pool_t *subpool = svn_pool_create(pool);
854
855   /* Expect no more arguments. */
856   SVN_ERR(parse_args(NULL, os, 0, 0, pool));
857
858   SVN_ERR(open_repos(&repos, opt_state->repository_path, opt_state, pool));
859   fs = svn_repos_fs(repos);
860   SVN_ERR(svn_fs_youngest_rev(&youngest, fs, pool));
861
862   /* Find the revision numbers at which to start and end. */
863   SVN_ERR(get_revnum(&start, &opt_state->start_revision,
864                      youngest, repos, pool));
865   SVN_ERR(get_revnum(&end, &opt_state->end_revision,
866                      youngest, repos, pool));
867
868   /* Fill in implied revisions if necessary. */
869   if (start == SVN_INVALID_REVNUM)
870     start = youngest;
871   if (end == SVN_INVALID_REVNUM)
872     end = start;
873
874   if (start > end)
875     return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
876        _("First revision cannot be higher than second"));
877
878   /* Loop over the requested revision range, performing the
879      predecessor deltification on paths changed in each. */
880   for (revision = start; revision <= end; revision++)
881     {
882       svn_pool_clear(subpool);
883       SVN_ERR(check_cancel(NULL));
884       if (! opt_state->quiet)
885         SVN_ERR(svn_cmdline_printf(subpool, _("Deltifying revision %ld..."),
886                                    revision));
887       SVN_ERR(svn_fs_deltify_revision(fs, revision, subpool));
888       if (! opt_state->quiet)
889         SVN_ERR(svn_cmdline_printf(subpool, _("done.\n")));
890     }
891   svn_pool_destroy(subpool);
892
893   return SVN_NO_ERROR;
894 }
895
896 /* Structure for errors encountered during 'svnadmin verify --keep-going'. */
897 struct verification_error
898 {
899   svn_revnum_t rev;
900   svn_error_t *err;
901 };
902
903 /* Pool cleanup function to clear an svn_error_t *. */
904 static apr_status_t
905 err_cleanup(void *data)
906 {
907   svn_error_t *err = data;
908
909   svn_error_clear(err);
910
911   return APR_SUCCESS;
912 }
913
914 struct repos_verify_callback_baton
915 {
916   /* Should we continue after receiving a first verification error? */
917   svn_boolean_t keep_going;
918
919   /* List of errors encountered during 'svnadmin verify --keep-going'. */
920   apr_array_header_t *error_summary;
921
922   /* Pool for data collected during callback invocations. */
923   apr_pool_t *result_pool;
924 };
925
926 /* Implementation of svn_repos_verify_callback_t to handle errors coming
927    from svn_repos_verify_fs3(). */
928 static svn_error_t *
929 repos_verify_callback(void *baton,
930                       svn_revnum_t revision,
931                       svn_error_t *verify_err,
932                       apr_pool_t *scratch_pool)
933 {
934   struct repos_verify_callback_baton *b = baton;
935
936   if (revision == SVN_INVALID_REVNUM)
937     {
938       SVN_ERR(svn_cmdline_fputs(_("* Error verifying repository metadata.\n"),
939                                 stderr, scratch_pool));
940     }
941   else
942     {
943       SVN_ERR(svn_cmdline_fprintf(stderr, scratch_pool,
944                                   _("* Error verifying revision %ld.\n"),
945                                   revision));
946     }
947
948   if (b->keep_going)
949     {
950       struct verification_error *verr;
951
952       svn_handle_error2(verify_err, stderr, FALSE, "svnadmin: ");
953
954       /* Remember the error in B->ERROR_SUMMARY. */
955       verr = apr_palloc(b->result_pool, sizeof(*verr));
956       verr->rev = revision;
957       verr->err = svn_error_dup(verify_err);
958       apr_pool_cleanup_register(b->result_pool, verr->err, err_cleanup,
959                                 apr_pool_cleanup_null);
960       APR_ARRAY_PUSH(b->error_summary, struct verification_error *) = verr;
961
962       return SVN_NO_ERROR;
963     }
964   else
965     return svn_error_trace(svn_error_dup(verify_err));
966 }
967
968 /* Implementation of svn_repos_notify_func_t to wrap the output to a
969    response stream for svn_repos_dump_fs2(), svn_repos_verify_fs(),
970    svn_repos_hotcopy3() and others. */
971 static void
972 repos_notify_handler(void *baton,
973                      const svn_repos_notify_t *notify,
974                      apr_pool_t *scratch_pool)
975 {
976   svn_stream_t *feedback_stream = baton;
977
978   switch (notify->action)
979   {
980     case svn_repos_notify_warning:
981       svn_error_clear(svn_stream_printf(feedback_stream, scratch_pool,
982                                         "WARNING 0x%04x: %s\n", notify->warning,
983                                         notify->warning_str));
984       return;
985
986     case svn_repos_notify_dump_rev_end:
987       svn_error_clear(svn_stream_printf(feedback_stream, scratch_pool,
988                                         _("* Dumped revision %ld.\n"),
989                                         notify->revision));
990       return;
991
992     case svn_repos_notify_verify_rev_end:
993       svn_error_clear(svn_stream_printf(feedback_stream, scratch_pool,
994                                         _("* Verified revision %ld.\n"),
995                                         notify->revision));
996       return;
997
998     case svn_repos_notify_verify_rev_structure:
999       if (notify->revision == SVN_INVALID_REVNUM)
1000         svn_error_clear(svn_stream_puts(feedback_stream,
1001                                 _("* Verifying repository metadata ...\n")));
1002       else
1003         svn_error_clear(svn_stream_printf(feedback_stream, scratch_pool,
1004                         _("* Verifying metadata at revision %ld ...\n"),
1005                         notify->revision));
1006       return;
1007
1008     case svn_repos_notify_pack_shard_start:
1009       {
1010         const char *shardstr = apr_psprintf(scratch_pool,
1011                                             "%" APR_INT64_T_FMT,
1012                                             notify->shard);
1013         svn_error_clear(svn_stream_printf(feedback_stream, scratch_pool,
1014                                           _("Packing revisions in shard %s..."),
1015                                           shardstr));
1016       }
1017       return;
1018
1019     case svn_repos_notify_pack_shard_end:
1020       svn_error_clear(svn_stream_puts(feedback_stream, _("done.\n")));
1021       return;
1022
1023     case svn_repos_notify_pack_shard_start_revprop:
1024       {
1025         const char *shardstr = apr_psprintf(scratch_pool,
1026                                             "%" APR_INT64_T_FMT,
1027                                             notify->shard);
1028         svn_error_clear(svn_stream_printf(feedback_stream, scratch_pool,
1029                                           _("Packing revprops in shard %s..."),
1030                                           shardstr));
1031       }
1032       return;
1033
1034     case svn_repos_notify_pack_shard_end_revprop:
1035       svn_error_clear(svn_stream_puts(feedback_stream, _("done.\n")));
1036       return;
1037
1038     case svn_repos_notify_load_txn_committed:
1039       if (notify->old_revision == SVN_INVALID_REVNUM)
1040         {
1041           svn_error_clear(svn_stream_printf(feedback_stream, scratch_pool,
1042                             _("\n------- Committed revision %ld >>>\n\n"),
1043                             notify->new_revision));
1044         }
1045       else
1046         {
1047           svn_error_clear(svn_stream_printf(feedback_stream, scratch_pool,
1048                             _("\n------- Committed new rev %ld"
1049                               " (loaded from original rev %ld"
1050                               ") >>>\n\n"), notify->new_revision,
1051                               notify->old_revision));
1052         }
1053       return;
1054
1055     case svn_repos_notify_load_node_start:
1056       {
1057         switch (notify->node_action)
1058         {
1059           case svn_node_action_change:
1060             svn_error_clear(svn_stream_printf(feedback_stream, scratch_pool,
1061                                   _("     * editing path : %s ..."),
1062                                   notify->path));
1063             break;
1064
1065           case svn_node_action_delete:
1066             svn_error_clear(svn_stream_printf(feedback_stream, scratch_pool,
1067                                   _("     * deleting path : %s ..."),
1068                                   notify->path));
1069             break;
1070
1071           case svn_node_action_add:
1072             svn_error_clear(svn_stream_printf(feedback_stream, scratch_pool,
1073                                   _("     * adding path : %s ..."),
1074                                   notify->path));
1075             break;
1076
1077           case svn_node_action_replace:
1078             svn_error_clear(svn_stream_printf(feedback_stream, scratch_pool,
1079                                   _("     * replacing path : %s ..."),
1080                                   notify->path));
1081             break;
1082
1083         }
1084       }
1085       return;
1086
1087     case svn_repos_notify_load_node_done:
1088       svn_error_clear(svn_stream_puts(feedback_stream, _(" done.\n")));
1089       return;
1090
1091     case svn_repos_notify_load_copied_node:
1092       svn_error_clear(svn_stream_puts(feedback_stream, "COPIED..."));
1093       return;
1094
1095     case svn_repos_notify_load_txn_start:
1096       svn_error_clear(svn_stream_printf(feedback_stream, scratch_pool,
1097                                 _("<<< Started new transaction, based on "
1098                                   "original revision %ld\n"),
1099                                 notify->old_revision));
1100       return;
1101
1102     case svn_repos_notify_load_skipped_rev:
1103       svn_error_clear(svn_stream_printf(feedback_stream, scratch_pool,
1104                                 _("<<< Skipped original revision %ld\n"),
1105                                 notify->old_revision));
1106       return;
1107
1108     case svn_repos_notify_load_normalized_mergeinfo:
1109       svn_error_clear(svn_stream_printf(feedback_stream, scratch_pool,
1110                                 _(" removing '\\r' from %s ..."),
1111                                 SVN_PROP_MERGEINFO));
1112       return;
1113
1114     case svn_repos_notify_mutex_acquired:
1115       svn_cmdline__setup_cancellation_handler();
1116       return;
1117
1118     case svn_repos_notify_recover_start:
1119       svn_error_clear(svn_stream_puts(feedback_stream,
1120                              _("Repository lock acquired.\n"
1121                                "Please wait; recovering the"
1122                                " repository may take some time...\n")));
1123       return;
1124
1125     case svn_repos_notify_upgrade_start:
1126       svn_error_clear(svn_stream_puts(feedback_stream,
1127                              _("Repository lock acquired.\n"
1128                                "Please wait; upgrading the"
1129                                " repository may take some time...\n")));
1130       return;
1131
1132     case svn_repos_notify_pack_revprops:
1133       {
1134         const char *shardstr = apr_psprintf(scratch_pool,
1135                                             "%" APR_INT64_T_FMT,
1136                                             notify->shard);
1137         svn_error_clear(svn_stream_printf(feedback_stream, scratch_pool,
1138                               _("Packed revision properties in shard %s\n"),
1139                               shardstr));
1140         return;
1141       }
1142
1143     case svn_repos_notify_cleanup_revprops:
1144       {
1145         const char *shardstr = apr_psprintf(scratch_pool,
1146                                             "%" APR_INT64_T_FMT,
1147                                             notify->shard);
1148         svn_error_clear(svn_stream_printf(feedback_stream, scratch_pool,
1149                               _("Removed non-packed revision properties"
1150                                 " in shard %s\n"),
1151                               shardstr));
1152         return;
1153       }
1154
1155     case svn_repos_notify_format_bumped:
1156       svn_error_clear(svn_stream_printf(feedback_stream, scratch_pool,
1157                             _("Bumped repository format to %ld\n"),
1158                             notify->revision));
1159       return;
1160
1161     case svn_repos_notify_hotcopy_rev_range:
1162       if (notify->start_revision == notify->end_revision)
1163         {
1164           svn_error_clear(svn_stream_printf(feedback_stream, scratch_pool,
1165                                             _("* Copied revision %ld.\n"),
1166                                             notify->start_revision));
1167         }
1168       else
1169         {
1170           svn_error_clear(svn_stream_printf(feedback_stream, scratch_pool,
1171                                _("* Copied revisions from %ld to %ld.\n"),
1172                                notify->start_revision, notify->end_revision));
1173         }
1174       return;
1175
1176     case svn_repos_notify_pack_noop:
1177       /* For best backward compatibility, we keep silent if there were just
1178          no more shards to pack. */
1179       if (notify->shard == -1)
1180         {
1181           svn_error_clear(svn_stream_printf(feedback_stream, scratch_pool,
1182                      _("svnadmin: Warning - this repository is not sharded."
1183                        " Packing has no effect.\n")));
1184         }
1185       return;
1186
1187     case svn_repos_notify_load_revprop_set:
1188       svn_error_clear(svn_stream_printf(feedback_stream, scratch_pool,
1189                         _("Properties set on revision %ld.\n"),
1190                         notify->new_revision));
1191       return;
1192
1193     default:
1194       return;
1195   }
1196 }
1197
1198
1199 /* Baton for recode_write(). */
1200 struct recode_write_baton
1201 {
1202   apr_pool_t *pool;
1203   FILE *out;
1204 };
1205
1206 /* This implements the 'svn_write_fn_t' interface.
1207
1208    Write DATA to ((struct recode_write_baton *) BATON)->out, in the
1209    console encoding, using svn_cmdline_fprintf().  DATA is a
1210    UTF8-encoded C string, therefore ignore LEN.
1211
1212    ### This recoding mechanism might want to be abstracted into
1213    ### svn_io.h or svn_cmdline.h, if it proves useful elsewhere. */
1214 static svn_error_t *recode_write(void *baton,
1215                                  const char *data,
1216                                  apr_size_t *len)
1217 {
1218   struct recode_write_baton *rwb = baton;
1219   svn_pool_clear(rwb->pool);
1220   return svn_cmdline_fputs(data, rwb->out, rwb->pool);
1221 }
1222
1223 /* Create a stream, to write to STD_STREAM, that uses recode_write()
1224    to perform UTF-8 to console encoding translation. */
1225 static svn_stream_t *
1226 recode_stream_create(FILE *std_stream, apr_pool_t *pool)
1227 {
1228   struct recode_write_baton *std_stream_rwb =
1229     apr_palloc(pool, sizeof(struct recode_write_baton));
1230
1231   svn_stream_t *rw_stream = svn_stream_create(std_stream_rwb, pool);
1232   std_stream_rwb->pool = svn_pool_create(pool);
1233   std_stream_rwb->out = std_stream;
1234   svn_stream_set_write(rw_stream, recode_write);
1235   return rw_stream;
1236 }
1237
1238 /* Read the min / max revision from the OPT_STATE, verify them against REPOS
1239    and return them in *LOWER and *UPPER, respectively.  Use SCRATCH_POOL
1240    for temporary allocations. */
1241 static svn_error_t *
1242 get_dump_range(svn_revnum_t *lower,
1243                svn_revnum_t *upper,
1244                svn_repos_t *repos,
1245                struct svnadmin_opt_state *opt_state,
1246                apr_pool_t *scratch_pool)
1247 {
1248   svn_fs_t *fs;
1249   svn_revnum_t youngest;
1250
1251   *lower = SVN_INVALID_REVNUM;
1252   *upper = SVN_INVALID_REVNUM;
1253
1254   fs = svn_repos_fs(repos);
1255   SVN_ERR(svn_fs_youngest_rev(&youngest, fs, scratch_pool));
1256
1257   /* Find the revision numbers at which to start and end. */
1258   SVN_ERR(get_revnum(lower, &opt_state->start_revision,
1259                      youngest, repos, scratch_pool));
1260   SVN_ERR(get_revnum(upper, &opt_state->end_revision,
1261                      youngest, repos, scratch_pool));
1262
1263   /* Fill in implied revisions if necessary. */
1264   if (*lower == SVN_INVALID_REVNUM)
1265     {
1266       *lower = 0;
1267       *upper = youngest;
1268     }
1269   else if (*upper == SVN_INVALID_REVNUM)
1270     {
1271       *upper = *lower;
1272     }
1273
1274   if (*lower > *upper)
1275     return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
1276        _("First revision cannot be higher than second"));
1277
1278   return SVN_NO_ERROR;
1279 }
1280
1281 /* Compare the node-path PATH with the (const char *) prefixes in PFXLIST.
1282  * Return TRUE if any prefix is a prefix of PATH (matching whole path
1283  * components); FALSE otherwise.
1284  * PATH starts with a '/', as do the (const char *) paths in PREFIXES. */
1285 /* This function is a duplicate of svndumpfilter.c:ary_prefix_match(). */
1286 static svn_boolean_t
1287 ary_prefix_match(const apr_array_header_t *pfxlist, const char *path)
1288 {
1289   int i;
1290   size_t path_len = strlen(path);
1291
1292   for (i = 0; i < pfxlist->nelts; i++)
1293     {
1294       const char *pfx = APR_ARRAY_IDX(pfxlist, i, const char *);
1295       size_t pfx_len = strlen(pfx);
1296
1297       if (path_len < pfx_len)
1298         continue;
1299       if (strncmp(path, pfx, pfx_len) == 0
1300           && (pfx_len == 1 || path[pfx_len] == '\0' || path[pfx_len] == '/'))
1301         return TRUE;
1302     }
1303
1304   return FALSE;
1305 }
1306
1307 /* Baton for dump_filter_func(). */
1308 struct dump_filter_baton_t
1309 {
1310   apr_array_header_t *prefixes;
1311   svn_boolean_t glob;
1312   svn_boolean_t do_exclude;
1313 };
1314
1315 /* Implements svn_repos_dump_filter_func_t. */
1316 static svn_error_t *
1317 dump_filter_func(svn_boolean_t *include,
1318                  svn_fs_root_t *root,
1319                  const char *path,
1320                  void *baton,
1321                  apr_pool_t *scratch_pool)
1322 {
1323   struct dump_filter_baton_t *b = baton;
1324   const svn_boolean_t matches =
1325     (b->glob
1326      ? svn_cstring_match_glob_list(path, b->prefixes)
1327      : ary_prefix_match(b->prefixes, path));
1328
1329   *include = b->do_exclude ? !matches : matches;
1330   return SVN_NO_ERROR;
1331 }
1332
1333 /* This implements `svn_opt_subcommand_t'. */
1334 static svn_error_t *
1335 subcommand_dump(apr_getopt_t *os, void *baton, apr_pool_t *pool)
1336 {
1337   struct svnadmin_opt_state *opt_state = baton;
1338   svn_repos_t *repos;
1339   svn_stream_t *out_stream;
1340   svn_revnum_t lower, upper;
1341   svn_stream_t *feedback_stream = NULL;
1342   struct dump_filter_baton_t filter_baton = {0};
1343
1344   /* Expect no more arguments. */
1345   SVN_ERR(parse_args(NULL, os, 0, 0, pool));
1346
1347   SVN_ERR(open_repos(&repos, opt_state->repository_path, opt_state, pool));
1348   SVN_ERR(get_dump_range(&lower, &upper, repos, opt_state, pool));
1349
1350   /* Open the file or STDOUT, depending on whether -F was specified. */
1351   if (opt_state->file)
1352     {
1353       apr_file_t *file;
1354
1355       /* Overwrite existing files, same as with > redirection. */
1356       SVN_ERR(svn_io_file_open(&file, opt_state->file,
1357                                APR_WRITE | APR_CREATE | APR_TRUNCATE
1358                                | APR_BUFFERED, APR_OS_DEFAULT, pool));
1359       out_stream = svn_stream_from_aprfile2(file, FALSE, pool);
1360     }
1361   else
1362     SVN_ERR(svn_stream_for_stdout(&out_stream, pool));
1363
1364   /* Progress feedback goes to STDERR, unless they asked to suppress it. */
1365   if (! opt_state->quiet)
1366     feedback_stream = recode_stream_create(stderr, pool);
1367
1368   /* Initialize the filter baton. */
1369   filter_baton.glob = opt_state->glob;
1370
1371   if (opt_state->exclude && !opt_state->include)
1372     {
1373       filter_baton.prefixes = opt_state->exclude;
1374       filter_baton.do_exclude = TRUE;
1375     }
1376   else if (opt_state->include && !opt_state->exclude)
1377     {
1378       filter_baton.prefixes = opt_state->include;
1379       filter_baton.do_exclude = FALSE;
1380     }
1381   else if (opt_state->include && opt_state->exclude)
1382     {
1383       return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
1384                                _("'--exclude' and '--include' options "
1385                                  "cannot be used simultaneously"));
1386     }
1387
1388   SVN_ERR(svn_repos_dump_fs4(repos, out_stream, lower, upper,
1389                              opt_state->incremental, opt_state->use_deltas,
1390                              TRUE, TRUE,
1391                              !opt_state->quiet ? repos_notify_handler : NULL,
1392                              feedback_stream,
1393                              filter_baton.prefixes ? dump_filter_func : NULL,
1394                              &filter_baton,
1395                              check_cancel, NULL, pool));
1396
1397   return SVN_NO_ERROR;
1398 }
1399
1400 /* This implements `svn_opt_subcommand_t'. */
1401 static svn_error_t *
1402 subcommand_dump_revprops(apr_getopt_t *os, void *baton, apr_pool_t *pool)
1403 {
1404   struct svnadmin_opt_state *opt_state = baton;
1405   svn_repos_t *repos;
1406   svn_stream_t *out_stream;
1407   svn_revnum_t lower, upper;
1408   svn_stream_t *feedback_stream = NULL;
1409
1410   /* Expect no more arguments. */
1411   SVN_ERR(parse_args(NULL, os, 0, 0, pool));
1412
1413   SVN_ERR(open_repos(&repos, opt_state->repository_path, opt_state, pool));
1414   SVN_ERR(get_dump_range(&lower, &upper, repos, opt_state, pool));
1415
1416   /* Open the file or STDOUT, depending on whether -F was specified. */
1417   if (opt_state->file)
1418     {
1419       apr_file_t *file;
1420
1421       /* Overwrite existing files, same as with > redirection. */
1422       SVN_ERR(svn_io_file_open(&file, opt_state->file,
1423                                APR_WRITE | APR_CREATE | APR_TRUNCATE
1424                                | APR_BUFFERED, APR_OS_DEFAULT, pool));
1425       out_stream = svn_stream_from_aprfile2(file, FALSE, pool);
1426     }
1427   else
1428     SVN_ERR(svn_stream_for_stdout(&out_stream, pool));
1429
1430   /* Progress feedback goes to STDERR, unless they asked to suppress it. */
1431   if (! opt_state->quiet)
1432     feedback_stream = recode_stream_create(stderr, pool);
1433
1434   SVN_ERR(svn_repos_dump_fs4(repos, out_stream, lower, upper,
1435                              FALSE, FALSE, TRUE, FALSE,
1436                              !opt_state->quiet ? repos_notify_handler : NULL,
1437                              feedback_stream, NULL, NULL,
1438                              check_cancel, NULL, pool));
1439
1440   return SVN_NO_ERROR;
1441 }
1442
1443 struct freeze_baton_t {
1444   const char *command;
1445   const char **args;
1446   int status;
1447 };
1448
1449 /* Implements svn_repos_freeze_func_t */
1450 static svn_error_t *
1451 freeze_body(void *baton,
1452             apr_pool_t *pool)
1453 {
1454   struct freeze_baton_t *b = baton;
1455   apr_status_t apr_err;
1456   apr_file_t *infile, *outfile, *errfile;
1457
1458   apr_err = apr_file_open_stdin(&infile, pool);
1459   if (apr_err)
1460     return svn_error_wrap_apr(apr_err, "Can't open stdin");
1461   apr_err = apr_file_open_stdout(&outfile, pool);
1462   if (apr_err)
1463     return svn_error_wrap_apr(apr_err, "Can't open stdout");
1464   apr_err = apr_file_open_stderr(&errfile, pool);
1465   if (apr_err)
1466     return svn_error_wrap_apr(apr_err, "Can't open stderr");
1467
1468   SVN_ERR(svn_io_run_cmd(NULL, b->command, b->args, &b->status,
1469                          NULL, TRUE,
1470                          infile, outfile, errfile, pool));
1471
1472   return SVN_NO_ERROR;
1473 }
1474
1475 static svn_error_t *
1476 subcommand_freeze(apr_getopt_t *os, void *baton, apr_pool_t *pool)
1477 {
1478   struct svnadmin_opt_state *opt_state = baton;
1479   apr_array_header_t *paths;
1480   apr_array_header_t *args;
1481   int i;
1482   struct freeze_baton_t b;
1483
1484   SVN_ERR(parse_args(&args, os, -1, -1, pool));
1485
1486   if (!args->nelts)
1487     return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, 0,
1488                             _("No program provided"));
1489
1490   if (!opt_state->file)
1491     {
1492       /* One repository on the command line. */
1493       paths = apr_array_make(pool, 1, sizeof(const char *));
1494       APR_ARRAY_PUSH(paths, const char *) = opt_state->repository_path;
1495     }
1496   else
1497     {
1498       svn_stringbuf_t *buf;
1499       const char *utf8;
1500       /* Read repository paths from the -F file. */
1501       SVN_ERR(svn_stringbuf_from_file2(&buf, opt_state->file, pool));
1502       SVN_ERR(svn_utf_cstring_to_utf8(&utf8, buf->data, pool));
1503       paths = svn_cstring_split(utf8, "\r\n", FALSE, pool);
1504     }
1505
1506   b.command = APR_ARRAY_IDX(args, 0, const char *);
1507   b.args = apr_palloc(pool, sizeof(char *) * (args->nelts + 1));
1508   for (i = 0; i < args->nelts; ++i)
1509     b.args[i] = APR_ARRAY_IDX(args, i, const char *);
1510   b.args[args->nelts] = NULL;
1511
1512   SVN_ERR(svn_repos_freeze(paths, freeze_body, &b, pool));
1513
1514   /* Make any non-zero status visible to the user. */
1515   if (b.status)
1516     exit(b.status);
1517
1518   return SVN_NO_ERROR;
1519 }
1520
1521
1522 /* This implements `svn_opt_subcommand_t'. */
1523 static svn_error_t *
1524 subcommand_help(apr_getopt_t *os, void *baton, apr_pool_t *pool)
1525 {
1526   struct svnadmin_opt_state *opt_state = baton;
1527   const char *header =
1528     _("general usage: svnadmin SUBCOMMAND REPOS_PATH  [ARGS & OPTIONS ...]\n"
1529       "Subversion repository administration tool.\n"
1530       "Type 'svnadmin help <subcommand>' for help on a specific subcommand.\n"
1531       "Type 'svnadmin --version' to see the program version and FS modules.\n"
1532       "\n"
1533       "Available subcommands:\n");
1534
1535   const char *fs_desc_start
1536     = _("The following repository back-end (FS) modules are available:\n\n");
1537
1538   svn_stringbuf_t *version_footer;
1539
1540   version_footer = svn_stringbuf_create(fs_desc_start, pool);
1541   SVN_ERR(svn_fs_print_modules(version_footer, pool));
1542
1543   SVN_ERR(svn_opt_print_help4(os, "svnadmin",
1544                               opt_state ? opt_state->version : FALSE,
1545                               opt_state ? opt_state->quiet : FALSE,
1546                               /*###opt_state ? opt_state->verbose :*/ FALSE,
1547                               version_footer->data,
1548                               header, cmd_table, options_table, NULL, NULL,
1549                               pool));
1550
1551   return SVN_NO_ERROR;
1552 }
1553
1554
1555 /* Set *REVNUM to the revision number of a numeric REV, or to
1556    SVN_INVALID_REVNUM if REV is unspecified. */
1557 static svn_error_t *
1558 optrev_to_revnum(svn_revnum_t *revnum, const svn_opt_revision_t *opt_rev)
1559 {
1560   if (opt_rev->kind == svn_opt_revision_number)
1561     {
1562       *revnum = opt_rev->value.number;
1563       if (! SVN_IS_VALID_REVNUM(*revnum))
1564         return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
1565                                  _("Invalid revision number (%ld) specified"),
1566                                  *revnum);
1567     }
1568   else if (opt_rev->kind == svn_opt_revision_unspecified)
1569     {
1570       *revnum = SVN_INVALID_REVNUM;
1571     }
1572   else
1573     {
1574       return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
1575                               _("Non-numeric revision specified"));
1576     }
1577   return SVN_NO_ERROR;
1578 }
1579
1580 /* Read the min / max revision from the OPT_STATE, verify them and return
1581    them in *LOWER and *UPPER, respectively. */
1582 static svn_error_t *
1583 get_load_range(svn_revnum_t *lower,
1584                svn_revnum_t *upper,
1585                struct svnadmin_opt_state *opt_state)
1586 {
1587   /* Find the revision numbers at which to start and end.  We only
1588      support a limited set of revision kinds: number and unspecified. */
1589   SVN_ERR(optrev_to_revnum(lower, &opt_state->start_revision));
1590   SVN_ERR(optrev_to_revnum(upper, &opt_state->end_revision));
1591
1592   /* Fill in implied revisions if necessary. */
1593   if ((*upper == SVN_INVALID_REVNUM) && (*lower != SVN_INVALID_REVNUM))
1594     {
1595       *upper = *lower;
1596     }
1597   else if ((*upper != SVN_INVALID_REVNUM) && (*lower == SVN_INVALID_REVNUM))
1598     {
1599       *lower = *upper;
1600     }
1601
1602   /* Ensure correct range ordering. */
1603   if (*lower > *upper)
1604     {
1605       return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
1606                               _("First revision cannot be higher than second"));
1607     }
1608
1609   return SVN_NO_ERROR;
1610 }
1611
1612
1613 /* This implements `svn_opt_subcommand_t'. */
1614 static svn_error_t *
1615 subcommand_load(apr_getopt_t *os, void *baton, apr_pool_t *pool)
1616 {
1617   svn_error_t *err;
1618   struct svnadmin_opt_state *opt_state = baton;
1619   svn_repos_t *repos;
1620   svn_revnum_t lower, upper;
1621   svn_stream_t *in_stream;
1622   svn_stream_t *feedback_stream = NULL;
1623
1624   /* Expect no more arguments. */
1625   SVN_ERR(parse_args(NULL, os, 0, 0, pool));
1626
1627   /* Find the revision numbers at which to start and end.  We only
1628      support a limited set of revision kinds: number and unspecified. */
1629   SVN_ERR(get_load_range(&lower, &upper, opt_state));
1630
1631   SVN_ERR(open_repos(&repos, opt_state->repository_path, opt_state, pool));
1632
1633   /* Open the file or STDIN, depending on whether -F was specified. */
1634   if (opt_state->file)
1635     SVN_ERR(svn_stream_open_readonly(&in_stream, opt_state->file,
1636                                      pool, pool));
1637   else
1638     SVN_ERR(svn_stream_for_stdin2(&in_stream, TRUE, pool));
1639
1640   /* Progress feedback goes to STDOUT, unless they asked to suppress it. */
1641   if (! opt_state->quiet)
1642     feedback_stream = recode_stream_create(stdout, pool);
1643
1644   err = svn_repos_load_fs6(repos, in_stream, lower, upper,
1645                            opt_state->uuid_action, opt_state->parent_dir,
1646                            opt_state->use_pre_commit_hook,
1647                            opt_state->use_post_commit_hook,
1648                            !opt_state->bypass_prop_validation,
1649                            opt_state->ignore_dates,
1650                            opt_state->normalize_props,
1651                            opt_state->quiet ? NULL : repos_notify_handler,
1652                            feedback_stream, check_cancel, NULL, pool);
1653
1654   if (svn_error_find_cause(err, SVN_ERR_BAD_PROPERTY_VALUE_EOL))
1655     {
1656       return svn_error_quick_wrap(err,
1657                                   _("A property with invalid line ending "
1658                                     "found in dumpstream; consider using "
1659                                     "--normalize-props while loading."));
1660     }
1661   else if (err && err->apr_err == SVN_ERR_BAD_PROPERTY_VALUE)
1662     {
1663       return svn_error_quick_wrap(err,
1664                                   _("Invalid property value found in "
1665                                     "dumpstream; consider repairing the "
1666                                     "source or using --bypass-prop-validation "
1667                                     "while loading."));
1668     }
1669
1670   return err;
1671 }
1672
1673 static svn_error_t *
1674 subcommand_load_revprops(apr_getopt_t *os, void *baton, apr_pool_t *pool)
1675 {
1676   svn_error_t *err;
1677   struct svnadmin_opt_state *opt_state = baton;
1678   svn_repos_t *repos;
1679   svn_revnum_t lower, upper;
1680   svn_stream_t *in_stream;
1681
1682   svn_stream_t *feedback_stream = NULL;
1683
1684   /* Expect no more arguments. */
1685   SVN_ERR(parse_args(NULL, os, 0, 0, pool));
1686
1687   /* Find the revision numbers at which to start and end.  We only
1688      support a limited set of revision kinds: number and unspecified. */
1689   SVN_ERR(get_load_range(&lower, &upper, opt_state));
1690
1691   SVN_ERR(open_repos(&repos, opt_state->repository_path, opt_state, pool));
1692
1693   /* Open the file or STDIN, depending on whether -F was specified. */
1694   if (opt_state->file)
1695     SVN_ERR(svn_stream_open_readonly(&in_stream, opt_state->file,
1696                                      pool, pool));
1697   else
1698     SVN_ERR(svn_stream_for_stdin2(&in_stream, TRUE, pool));
1699
1700   /* Progress feedback goes to STDOUT, unless they asked to suppress it. */
1701   if (! opt_state->quiet)
1702     feedback_stream = recode_stream_create(stdout, pool);
1703
1704   err = svn_repos_load_fs_revprops(repos, in_stream, lower, upper,
1705                                    !opt_state->bypass_prop_validation,
1706                                    opt_state->ignore_dates,
1707                                    opt_state->normalize_props,
1708                                    opt_state->quiet ? NULL
1709                                                     : repos_notify_handler,
1710                                    feedback_stream, check_cancel, NULL, pool);
1711
1712   if (svn_error_find_cause(err, SVN_ERR_BAD_PROPERTY_VALUE_EOL))
1713     {
1714       return svn_error_quick_wrap(err,
1715                                   _("A property with invalid line ending "
1716                                     "found in dumpstream; consider using "
1717                                     "--normalize-props while loading."));
1718     }
1719   else if (err && err->apr_err == SVN_ERR_BAD_PROPERTY_VALUE)
1720     {
1721       return svn_error_quick_wrap(err,
1722                                   _("Invalid property value found in "
1723                                     "dumpstream; consider repairing the "
1724                                     "source or using --bypass-prop-validation "
1725                                     "while loading."));
1726     }
1727
1728   return err;
1729 }
1730
1731 /* This implements `svn_opt_subcommand_t'. */
1732 static svn_error_t *
1733 subcommand_lstxns(apr_getopt_t *os, void *baton, apr_pool_t *pool)
1734 {
1735   struct svnadmin_opt_state *opt_state = baton;
1736   svn_repos_t *repos;
1737   svn_fs_t *fs;
1738   apr_array_header_t *txns;
1739   apr_pool_t *iterpool;
1740   svn_revnum_t youngest, limit;
1741   int i;
1742
1743   /* Expect no more arguments. */
1744   SVN_ERR(parse_args(NULL, os, 0, 0, pool));
1745   if (opt_state->end_revision.kind != svn_opt_revision_unspecified)
1746     return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
1747                              _("Revision range is not allowed"));
1748
1749   SVN_ERR(open_repos(&repos, opt_state->repository_path, opt_state, pool));
1750   fs = svn_repos_fs(repos);
1751   SVN_ERR(svn_fs_list_transactions(&txns, fs, pool));
1752
1753   SVN_ERR(svn_fs_youngest_rev(&youngest, fs, pool));
1754   SVN_ERR(get_revnum(&limit, &opt_state->start_revision, youngest, repos,
1755                      pool));
1756   
1757   iterpool = svn_pool_create(pool);
1758   for (i = 0; i < txns->nelts; i++)
1759     {
1760       const char *name = APR_ARRAY_IDX(txns, i, const char *);
1761       svn_boolean_t show = TRUE;
1762
1763       svn_pool_clear(iterpool);
1764       if (limit != SVN_INVALID_REVNUM)
1765         {
1766           svn_fs_txn_t *txn;
1767           svn_revnum_t base;
1768
1769           SVN_ERR(svn_fs_open_txn(&txn, fs, name, iterpool));
1770           base = svn_fs_txn_base_revision(txn);
1771
1772           if (base > limit)
1773             show = FALSE;
1774         }
1775       if (show)
1776         SVN_ERR(svn_cmdline_printf(pool, "%s\n", name));
1777     }
1778   svn_pool_destroy(iterpool);
1779
1780   return SVN_NO_ERROR;
1781 }
1782
1783
1784 /* This implements `svn_opt_subcommand_t'. */
1785 static svn_error_t *
1786 subcommand_recover(apr_getopt_t *os, void *baton, apr_pool_t *pool)
1787 {
1788   svn_revnum_t youngest_rev;
1789   svn_repos_t *repos;
1790   svn_error_t *err;
1791   struct svnadmin_opt_state *opt_state = baton;
1792   svn_stream_t *feedback_stream = NULL;
1793
1794   /* Expect no more arguments. */
1795   SVN_ERR(parse_args(NULL, os, 0, 0, pool));
1796
1797   SVN_ERR(svn_stream_for_stdout(&feedback_stream, pool));
1798
1799   /* Restore default signal handlers until after we have acquired the
1800    * exclusive lock so that the user interrupt before we actually
1801    * touch the repository. */
1802   svn_cmdline__disable_cancellation_handler();
1803
1804   err = svn_repos_recover4(opt_state->repository_path, TRUE,
1805                            repos_notify_handler, feedback_stream,
1806                            check_cancel, NULL, pool);
1807   if (err)
1808     {
1809       if (! APR_STATUS_IS_EAGAIN(err->apr_err))
1810         return err;
1811       svn_error_clear(err);
1812       if (! opt_state->wait)
1813         return svn_error_create(SVN_ERR_REPOS_LOCKED, NULL,
1814                                 _("Failed to get exclusive repository "
1815                                   "access; perhaps another process\n"
1816                                   "such as httpd, svnserve or svn "
1817                                   "has it open?"));
1818       SVN_ERR(svn_cmdline_printf(pool,
1819                                  _("Waiting on repository lock; perhaps"
1820                                    " another process has it open?\n")));
1821       SVN_ERR(svn_cmdline_fflush(stdout));
1822       SVN_ERR(svn_repos_recover4(opt_state->repository_path, FALSE,
1823                                  repos_notify_handler, feedback_stream,
1824                                  check_cancel, NULL, pool));
1825     }
1826
1827   SVN_ERR(svn_cmdline_printf(pool, _("\nRecovery completed.\n")));
1828
1829   /* Since db transactions may have been replayed, it's nice to tell
1830      people what the latest revision is.  It also proves that the
1831      recovery actually worked. */
1832   SVN_ERR(open_repos(&repos, opt_state->repository_path, opt_state, pool));
1833   SVN_ERR(svn_fs_youngest_rev(&youngest_rev, svn_repos_fs(repos), pool));
1834   SVN_ERR(svn_cmdline_printf(pool, _("The latest repos revision is %ld.\n"),
1835                              youngest_rev));
1836
1837   return SVN_NO_ERROR;
1838 }
1839
1840
1841 /* This implements `svn_opt_subcommand_t'. */
1842 static svn_error_t *
1843 list_dblogs(apr_getopt_t *os, void *baton, svn_boolean_t only_unused,
1844             apr_pool_t *pool)
1845 {
1846   struct svnadmin_opt_state *opt_state = baton;
1847   apr_array_header_t *logfiles;
1848   int i;
1849
1850   /* Expect no more arguments. */
1851   SVN_ERR(parse_args(NULL, os, 0, 0, pool));
1852
1853   SVN_ERR(svn_repos_db_logfiles(&logfiles,
1854                                 opt_state->repository_path,
1855                                 only_unused,
1856                                 pool));
1857
1858   /* Loop, printing log files.  We append the log paths to the
1859      repository path, making sure to return everything to the native
1860      style before printing. */
1861   for (i = 0; i < logfiles->nelts; i++)
1862     {
1863       const char *log_path;
1864       log_path = svn_dirent_join(opt_state->repository_path,
1865                                  APR_ARRAY_IDX(logfiles, i, const char *),
1866                                  pool);
1867       log_path = svn_dirent_local_style(log_path, pool);
1868       SVN_ERR(svn_cmdline_printf(pool, "%s\n", log_path));
1869     }
1870
1871   return SVN_NO_ERROR;
1872 }
1873
1874
1875 /* This implements `svn_opt_subcommand_t'. */
1876 static svn_error_t *
1877 subcommand_list_dblogs(apr_getopt_t *os, void *baton, apr_pool_t *pool)
1878 {
1879   SVN_ERR(list_dblogs(os, baton, FALSE, pool));
1880   return SVN_NO_ERROR;
1881 }
1882
1883
1884 /* This implements `svn_opt_subcommand_t'. */
1885 static svn_error_t *
1886 subcommand_list_unused_dblogs(apr_getopt_t *os, void *baton, apr_pool_t *pool)
1887 {
1888   /* Expect no more arguments. */
1889   SVN_ERR(parse_args(NULL, os, 0, 0, pool));
1890
1891   SVN_ERR(list_dblogs(os, baton, TRUE, pool));
1892   return SVN_NO_ERROR;
1893 }
1894
1895
1896 /* This implements `svn_opt_subcommand_t'. */
1897 static svn_error_t *
1898 subcommand_rmtxns(apr_getopt_t *os, void *baton, apr_pool_t *pool)
1899 {
1900   struct svnadmin_opt_state *opt_state = baton;
1901   svn_repos_t *repos;
1902   svn_fs_t *fs;
1903   svn_fs_txn_t *txn;
1904   apr_array_header_t *args;
1905   int i;
1906   apr_pool_t *subpool = svn_pool_create(pool);
1907
1908   SVN_ERR(parse_args(&args, os, -1, -1, pool));
1909
1910   SVN_ERR(open_repos(&repos, opt_state->repository_path, opt_state, pool));
1911   fs = svn_repos_fs(repos);
1912
1913   /* All the rest of the arguments are transaction names. */
1914   for (i = 0; i < args->nelts; i++)
1915     {
1916       const char *txn_name = APR_ARRAY_IDX(args, i, const char *);
1917       svn_error_t *err;
1918
1919       svn_pool_clear(subpool);
1920
1921       /* Try to open the txn.  If that succeeds, try to abort it. */
1922       err = svn_fs_open_txn(&txn, fs, txn_name, subpool);
1923       if (! err)
1924         err = svn_fs_abort_txn(txn, subpool);
1925
1926       /* If either the open or the abort of the txn fails because that
1927          transaction is dead, just try to purge the thing.  Else,
1928          there was either an error worth reporting, or not error at
1929          all.  */
1930       if (err && (err->apr_err == SVN_ERR_FS_TRANSACTION_DEAD))
1931         {
1932           svn_error_clear(err);
1933           err = svn_fs_purge_txn(fs, txn_name, subpool);
1934         }
1935
1936       /* If we had a real from the txn open, abort, or purge, we clear
1937          that error and just report to the user that we had an issue
1938          with this particular txn. */
1939       if (err)
1940         {
1941           svn_handle_error2(err, stderr, FALSE /* non-fatal */, "svnadmin: ");
1942           svn_error_clear(err);
1943         }
1944       else if (! opt_state->quiet)
1945         {
1946           SVN_ERR(svn_cmdline_printf(subpool, _("Transaction '%s' removed.\n"),
1947                                      txn_name));
1948         }
1949     }
1950
1951   svn_pool_destroy(subpool);
1952
1953   return SVN_NO_ERROR;
1954 }
1955
1956
1957 /* A helper for the 'setrevprop' and 'setlog' commands.  Expects
1958    OPT_STATE->txn_id, OPT_STATE->use_pre_revprop_change_hook and
1959    OPT_STATE->use_post_revprop_change_hook to be set appropriately.
1960    If FILENAME is NULL, delete property PROP_NAME.  */
1961 static svn_error_t *
1962 set_revprop(const char *prop_name, const char *filename,
1963             struct svnadmin_opt_state *opt_state, apr_pool_t *pool)
1964 {
1965   svn_repos_t *repos;
1966   svn_string_t *prop_value;
1967
1968   if (filename)
1969     {
1970       svn_stringbuf_t *file_contents;
1971
1972       SVN_ERR(svn_stringbuf_from_file2(&file_contents, filename, pool));
1973
1974       prop_value = svn_string_create_empty(pool);
1975       prop_value->data = file_contents->data;
1976       prop_value->len = file_contents->len;
1977
1978       SVN_ERR(svn_subst_translate_string2(&prop_value, NULL, NULL, prop_value,
1979                                           NULL, FALSE, pool, pool));
1980     }
1981   else
1982     {
1983       prop_value = NULL;
1984     }
1985
1986   /* Open the filesystem  */
1987   SVN_ERR(open_repos(&repos, opt_state->repository_path, opt_state, pool));
1988
1989   if (opt_state->txn_id)
1990     {
1991       svn_fs_t *fs = svn_repos_fs(repos);
1992       svn_fs_txn_t *txn;
1993
1994       SVN_ERR(svn_fs_open_txn(&txn, fs, opt_state->txn_id, pool));
1995       SVN_ERR(svn_fs_change_txn_prop(txn, prop_name, prop_value, pool));
1996     }
1997   else
1998     SVN_ERR(svn_repos_fs_change_rev_prop4(
1999               repos, opt_state->start_revision.value.number,
2000               NULL, prop_name, NULL, prop_value,
2001               opt_state->use_pre_revprop_change_hook,
2002               opt_state->use_post_revprop_change_hook,
2003               NULL, NULL, pool));
2004
2005   return SVN_NO_ERROR;
2006 }
2007
2008
2009 /* This implements `svn_opt_subcommand_t'. */
2010 static svn_error_t *
2011 subcommand_setrevprop(apr_getopt_t *os, void *baton, apr_pool_t *pool)
2012 {
2013   struct svnadmin_opt_state *opt_state = baton;
2014   apr_array_header_t *args;
2015   const char *prop_name, *filename;
2016
2017   /* Expect two more arguments: NAME FILE */
2018   SVN_ERR(parse_args(&args, os, 2, 2, pool));
2019   prop_name = APR_ARRAY_IDX(args, 0, const char *);
2020   filename = APR_ARRAY_IDX(args, 1, const char *);
2021   SVN_ERR(target_arg_to_dirent(&filename, filename, pool));
2022
2023   if (opt_state->txn_id)
2024     {
2025       if (opt_state->start_revision.kind != svn_opt_revision_unspecified
2026           || opt_state->end_revision.kind != svn_opt_revision_unspecified)
2027         return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
2028                                  _("--revision (-r) and --transaction (-t) "
2029                                    "are mutually exclusive"));
2030
2031       if (opt_state->use_pre_revprop_change_hook
2032           || opt_state->use_post_revprop_change_hook)
2033         return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
2034                                  _("Calling hooks is incompatible with "
2035                                    "--transaction (-t)"));
2036     }
2037   else if (opt_state->start_revision.kind != svn_opt_revision_number)
2038     return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
2039                              _("Missing revision"));
2040   else if (opt_state->end_revision.kind != svn_opt_revision_unspecified)
2041     return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
2042                              _("Only one revision allowed"));
2043
2044   return set_revprop(prop_name, filename, opt_state, pool);
2045 }
2046
2047
2048 /* This implements `svn_opt_subcommand_t'. */
2049 static svn_error_t *
2050 subcommand_setuuid(apr_getopt_t *os, void *baton, apr_pool_t *pool)
2051 {
2052   struct svnadmin_opt_state *opt_state = baton;
2053   apr_array_header_t *args;
2054   svn_repos_t *repos;
2055   svn_fs_t *fs;
2056   const char *uuid = NULL;
2057
2058   /* Expect zero or one more arguments: [UUID] */
2059   SVN_ERR(parse_args(&args, os, 0, 1, pool));
2060   if (args->nelts == 1)
2061     uuid = APR_ARRAY_IDX(args, 0, const char *);
2062
2063   SVN_ERR(open_repos(&repos, opt_state->repository_path, opt_state, pool));
2064   fs = svn_repos_fs(repos);
2065   return svn_fs_set_uuid(fs, uuid, pool);
2066 }
2067
2068
2069 /* This implements `svn_opt_subcommand_t'. */
2070 static svn_error_t *
2071 subcommand_setlog(apr_getopt_t *os, void *baton, apr_pool_t *pool)
2072 {
2073   struct svnadmin_opt_state *opt_state = baton;
2074   apr_array_header_t *args;
2075   const char *filename;
2076
2077   /* Expect one more argument: FILE */
2078   SVN_ERR(parse_args(&args, os, 1, 1, pool));
2079   filename = APR_ARRAY_IDX(args, 0, const char *);
2080   SVN_ERR(target_arg_to_dirent(&filename, filename, pool));
2081
2082   if (opt_state->start_revision.kind != svn_opt_revision_number)
2083     return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
2084                              _("Missing revision"));
2085   else if (opt_state->end_revision.kind != svn_opt_revision_unspecified)
2086     return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
2087                              _("Only one revision allowed"));
2088
2089   /* set_revprop() responds only to pre-/post-revprop-change opts. */
2090   if (!opt_state->bypass_hooks)
2091     {
2092       opt_state->use_pre_revprop_change_hook = TRUE;
2093       opt_state->use_post_revprop_change_hook = TRUE;
2094     }
2095
2096   return set_revprop(SVN_PROP_REVISION_LOG, filename, opt_state, pool);
2097 }
2098
2099
2100 /* This implements 'svn_opt_subcommand_t'. */
2101 static svn_error_t *
2102 subcommand_pack(apr_getopt_t *os, void *baton, apr_pool_t *pool)
2103 {
2104   struct svnadmin_opt_state *opt_state = baton;
2105   svn_repos_t *repos;
2106   svn_stream_t *feedback_stream = NULL;
2107
2108   /* Expect no more arguments. */
2109   SVN_ERR(parse_args(NULL, os, 0, 0, pool));
2110
2111   SVN_ERR(open_repos(&repos, opt_state->repository_path, opt_state, pool));
2112
2113   /* Progress feedback goes to STDOUT, unless they asked to suppress it. */
2114   if (! opt_state->quiet)
2115     feedback_stream = recode_stream_create(stdout, pool);
2116
2117   return svn_error_trace(
2118     svn_repos_fs_pack2(repos, !opt_state->quiet ? repos_notify_handler : NULL,
2119                        feedback_stream, check_cancel, NULL, pool));
2120 }
2121
2122
2123 /* This implements `svn_opt_subcommand_t'. */
2124 static svn_error_t *
2125 subcommand_verify(apr_getopt_t *os, void *baton, apr_pool_t *pool)
2126 {
2127   struct svnadmin_opt_state *opt_state = baton;
2128   svn_repos_t *repos;
2129   svn_fs_t *fs;
2130   svn_revnum_t youngest, lower, upper;
2131   svn_stream_t *feedback_stream = NULL;
2132   struct repos_verify_callback_baton verify_baton = { 0 };
2133
2134   /* Expect no more arguments. */
2135   SVN_ERR(parse_args(NULL, os, 0, 0, pool));
2136
2137   if (opt_state->txn_id
2138       && (opt_state->start_revision.kind != svn_opt_revision_unspecified
2139           || opt_state->end_revision.kind != svn_opt_revision_unspecified))
2140     {
2141       return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
2142                                _("--revision (-r) and --transaction (-t) "
2143                                  "are mutually exclusive"));
2144     }
2145
2146   SVN_ERR(open_repos(&repos, opt_state->repository_path, opt_state, pool));
2147   fs = svn_repos_fs(repos);
2148   SVN_ERR(svn_fs_youngest_rev(&youngest, fs, pool));
2149
2150   /* Usage 2. */
2151   if (opt_state->txn_id)
2152     {
2153       svn_fs_txn_t *txn;
2154       svn_fs_root_t *root;
2155
2156       SVN_ERR(svn_fs_open_txn(&txn, fs, opt_state->txn_id, pool));
2157       SVN_ERR(svn_fs_txn_root(&root, txn, pool));
2158       SVN_ERR(svn_fs_verify_root(root, pool));
2159       return SVN_NO_ERROR;
2160     }
2161   else
2162     /* Usage 1. */
2163     ;
2164
2165   /* Find the revision numbers at which to start and end. */
2166   SVN_ERR(get_revnum(&lower, &opt_state->start_revision,
2167                      youngest, repos, pool));
2168   SVN_ERR(get_revnum(&upper, &opt_state->end_revision,
2169                      youngest, repos, pool));
2170
2171   if (upper == SVN_INVALID_REVNUM)
2172     {
2173       upper = lower;
2174     }
2175
2176   if (!opt_state->quiet)
2177     feedback_stream = recode_stream_create(stdout, pool);
2178
2179   verify_baton.keep_going = opt_state->keep_going;
2180   verify_baton.error_summary =
2181     apr_array_make(pool, 0, sizeof(struct verification_error *));
2182   verify_baton.result_pool = pool;
2183
2184   SVN_ERR(svn_repos_verify_fs3(repos, lower, upper,
2185                                opt_state->check_normalization,
2186                                opt_state->metadata_only,
2187                                !opt_state->quiet
2188                                  ? repos_notify_handler : NULL,
2189                                feedback_stream,
2190                                repos_verify_callback, &verify_baton,
2191                                check_cancel, NULL, pool));
2192
2193   /* Show the --keep-going error summary. */
2194   if (!opt_state->quiet
2195       && opt_state->keep_going
2196       && verify_baton.error_summary->nelts > 0)
2197     {
2198       int rev_maxlength;
2199       svn_revnum_t end_revnum;
2200       apr_pool_t *iterpool;
2201       int i;
2202
2203       svn_error_clear(
2204         svn_stream_puts(feedback_stream,
2205                           _("\n-----Summary of corrupt revisions-----\n")));
2206
2207       /* The standard column width for the revision number is 6 characters.
2208          If the revision number can potentially be larger (i.e. if end_revnum
2209          is larger than 1000000), we increase the column width as needed. */
2210       rev_maxlength = 6;
2211       end_revnum = APR_ARRAY_IDX(verify_baton.error_summary,
2212                                  verify_baton.error_summary->nelts - 1,
2213                                  struct verification_error *)->rev;
2214       while (end_revnum >= 1000000)
2215         {
2216           rev_maxlength++;
2217           end_revnum = end_revnum / 10;
2218         }
2219
2220       iterpool = svn_pool_create(pool);
2221       for (i = 0; i < verify_baton.error_summary->nelts; i++)
2222         {
2223           struct verification_error *verr;
2224           svn_error_t *err;
2225           const char *rev_str;
2226
2227           svn_pool_clear(iterpool);
2228
2229           verr = APR_ARRAY_IDX(verify_baton.error_summary, i,
2230                                struct verification_error *);
2231
2232           if (verr->rev != SVN_INVALID_REVNUM)
2233             {
2234               rev_str = apr_psprintf(iterpool, "r%ld", verr->rev);
2235               rev_str = apr_psprintf(iterpool, "%*s", rev_maxlength, rev_str);
2236               for (err = svn_error_purge_tracing(verr->err);
2237                    err != SVN_NO_ERROR; err = err->child)
2238                 {
2239                   char buf[512];
2240                   const char *message;
2241
2242                   message = svn_err_best_message(err, buf, sizeof(buf));
2243                   svn_error_clear(svn_stream_printf(feedback_stream, iterpool,
2244                                                     "%s: E%06d: %s\n",
2245                                                     rev_str, err->apr_err,
2246                                                     message));
2247                 }
2248             }
2249         }
2250
2251        svn_pool_destroy(iterpool);
2252     }
2253
2254   if (verify_baton.error_summary->nelts > 0)
2255     {
2256       return svn_error_createf(SVN_ERR_CL_REPOS_VERIFY_FAILED, NULL,
2257                                _("Failed to verify repository '%s'"),
2258                                svn_dirent_local_style(
2259                                  opt_state->repository_path, pool));
2260     }
2261
2262   return SVN_NO_ERROR;
2263 }
2264
2265 /* This implements `svn_opt_subcommand_t'. */
2266 svn_error_t *
2267 subcommand_hotcopy(apr_getopt_t *os, void *baton, apr_pool_t *pool)
2268 {
2269   struct svnadmin_opt_state *opt_state = baton;
2270   svn_stream_t *feedback_stream = NULL;
2271   apr_array_header_t *targets;
2272   const char *new_repos_path;
2273
2274   /* Expect one more argument: NEW_REPOS_PATH */
2275   SVN_ERR(parse_args(&targets, os, 1, 1, pool));
2276   new_repos_path = APR_ARRAY_IDX(targets, 0, const char *);
2277   SVN_ERR(target_arg_to_dirent(&new_repos_path, new_repos_path, pool));
2278
2279   /* Progress feedback goes to STDOUT, unless they asked to suppress it. */
2280   if (! opt_state->quiet)
2281     feedback_stream = recode_stream_create(stdout, pool);
2282
2283   return svn_repos_hotcopy3(opt_state->repository_path, new_repos_path,
2284                             opt_state->clean_logs, opt_state->incremental,
2285                             !opt_state->quiet ? repos_notify_handler : NULL,
2286                             feedback_stream, check_cancel, NULL, pool);
2287 }
2288
2289 svn_error_t *
2290 subcommand_info(apr_getopt_t *os, void *baton, apr_pool_t *pool)
2291 {
2292   struct svnadmin_opt_state *opt_state = baton;
2293   svn_repos_t *repos;
2294   svn_fs_t *fs;
2295   int fs_format;
2296   const char *uuid;
2297   svn_revnum_t head_rev;
2298
2299   /* Expect no more arguments. */
2300   SVN_ERR(parse_args(NULL, os, 0, 0, pool));
2301
2302   SVN_ERR(open_repos(&repos, opt_state->repository_path, opt_state, pool));
2303   fs = svn_repos_fs(repos);
2304   SVN_ERR(svn_cmdline_printf(pool, _("Path: %s\n"),
2305                              svn_dirent_local_style(svn_repos_path(repos, pool),
2306                                                     pool)));
2307
2308   SVN_ERR(svn_fs_get_uuid(fs, &uuid, pool));
2309   SVN_ERR(svn_cmdline_printf(pool, _("UUID: %s\n"), uuid));
2310
2311   SVN_ERR(svn_fs_youngest_rev(&head_rev, fs, pool));
2312   SVN_ERR(svn_cmdline_printf(pool, _("Revisions: %ld\n"), head_rev));
2313   {
2314     int repos_format, minor;
2315     svn_version_t *repos_version, *fs_version;
2316     SVN_ERR(svn_repos_info_format(&repos_format, &repos_version,
2317                                   repos, pool, pool));
2318     SVN_ERR(svn_cmdline_printf(pool, _("Repository Format: %d\n"),
2319                                repos_format));
2320
2321     SVN_ERR(svn_fs_info_format(&fs_format, &fs_version,
2322                                fs, pool, pool));
2323     /* fs_format will be printed later. */
2324
2325     SVN_ERR_ASSERT(repos_version->major == SVN_VER_MAJOR);
2326     SVN_ERR_ASSERT(fs_version->major == SVN_VER_MAJOR);
2327     SVN_ERR_ASSERT(repos_version->patch == 0);
2328     SVN_ERR_ASSERT(fs_version->patch == 0);
2329
2330     minor = (repos_version->minor > fs_version->minor)
2331             ? repos_version->minor : fs_version->minor;
2332     SVN_ERR(svn_cmdline_printf(pool, _("Compatible With Version: %d.%d.0\n"),
2333                                SVN_VER_MAJOR, minor));
2334   }
2335
2336   {
2337     apr_hash_t *capabilities_set;
2338     apr_array_header_t *capabilities;
2339     int i;
2340
2341     SVN_ERR(svn_repos_capabilities(&capabilities_set, repos, pool, pool));
2342     capabilities = svn_sort__hash(capabilities_set,
2343                                   svn_sort_compare_items_lexically,
2344                                   pool);
2345
2346     for (i = 0; i < capabilities->nelts; i++)
2347       {
2348         svn_sort__item_t *item = &APR_ARRAY_IDX(capabilities, i,
2349                                                 svn_sort__item_t);
2350         const char *capability = item->key;
2351         SVN_ERR(svn_cmdline_printf(pool, _("Repository Capability: %s\n"),
2352                                    capability));
2353       }
2354   }
2355
2356   {
2357     const svn_fs_info_placeholder_t *info;
2358
2359     SVN_ERR(svn_fs_info(&info, fs, pool, pool));
2360     SVN_ERR(svn_cmdline_printf(pool, _("Filesystem Type: %s\n"),
2361                                info->fs_type));
2362     SVN_ERR(svn_cmdline_printf(pool, _("Filesystem Format: %d\n"),
2363                                fs_format));
2364     if (!strcmp(info->fs_type, SVN_FS_TYPE_FSFS))
2365       {
2366         const svn_fs_fsfs_info_t *fsfs_info = (const void *)info;
2367
2368         if (fsfs_info->shard_size)
2369           SVN_ERR(svn_cmdline_printf(pool, _("FSFS Sharded: yes\n")));
2370         else
2371           SVN_ERR(svn_cmdline_printf(pool, _("FSFS Sharded: no\n")));
2372
2373         if (fsfs_info->shard_size)
2374           SVN_ERR(svn_cmdline_printf(pool, _("FSFS Shard Size: %d\n"),
2375                                      fsfs_info->shard_size));
2376
2377         /* Print packing statistics, if enabled on the FS. */
2378         if (fsfs_info->shard_size)
2379           {
2380             const int shard_size = fsfs_info->shard_size;
2381             const long shards_packed = fsfs_info->min_unpacked_rev / shard_size;
2382             const long shards_full = (head_rev + 1) / shard_size;
2383             SVN_ERR(svn_cmdline_printf(pool, _("FSFS Shards Packed: %ld/%ld\n"),
2384                                        shards_packed, shards_full));
2385           }
2386
2387         if (fsfs_info->log_addressing)
2388           SVN_ERR(svn_cmdline_printf(pool, _("FSFS Logical Addressing: yes\n")));
2389         else
2390           SVN_ERR(svn_cmdline_printf(pool, _("FSFS Logical Addressing: no\n")));
2391       }
2392     else if (!strcmp(info->fs_type, SVN_FS_TYPE_FSX))
2393       {
2394         const svn_fs_fsx_info_t *fsx_info = (const void *)info;
2395
2396         const int shard_size = fsx_info->shard_size;
2397         const long shards_packed = fsx_info->min_unpacked_rev / shard_size;
2398         long shards_full = (head_rev + 1) / shard_size;
2399
2400         SVN_ERR(svn_cmdline_printf(pool, _("FSX Shard Size: %d\n"),
2401                                    shard_size));
2402         SVN_ERR(svn_cmdline_printf(pool, _("FSX Shards Packed: %ld/%ld\n"),
2403                                    shards_packed, shards_full));
2404       }
2405   }
2406
2407   {
2408     apr_array_header_t *files;
2409     int i;
2410
2411     SVN_ERR(svn_fs_info_config_files(&files, fs, pool, pool));
2412     for (i = 0; i < files->nelts; i++)
2413       SVN_ERR(svn_cmdline_printf(pool, _("Configuration File: %s\n"),
2414                                  svn_dirent_local_style(
2415                                    APR_ARRAY_IDX(files, i, const char *),
2416                                    pool)));
2417   }
2418
2419   /* 'svn info' prints an extra newline here, to support multiple targets.
2420      We'll do the same. */
2421   SVN_ERR(svn_cmdline_printf(pool, "\n"));
2422
2423   return SVN_NO_ERROR;
2424 }
2425
2426 /* This implements `svn_opt_subcommand_t'. */
2427 static svn_error_t *
2428 subcommand_lock(apr_getopt_t *os, void *baton, apr_pool_t *pool)
2429 {
2430   struct svnadmin_opt_state *opt_state = baton;
2431   svn_repos_t *repos;
2432   svn_fs_t *fs;
2433   svn_fs_access_t *access;
2434   apr_array_header_t *args;
2435   const char *username;
2436   const char *lock_path;
2437   const char *comment_file_name;
2438   svn_stringbuf_t *file_contents;
2439   svn_lock_t *lock;
2440   const char *lock_token = NULL;
2441
2442   /* Expect three more arguments: PATH USERNAME COMMENT-FILE */
2443   SVN_ERR(parse_args(&args, os, 3, 4, pool));
2444   lock_path = APR_ARRAY_IDX(args, 0, const char *);
2445   username = APR_ARRAY_IDX(args, 1, const char *);
2446   comment_file_name = APR_ARRAY_IDX(args, 2, const char *);
2447
2448   /* Expect one more optional argument: TOKEN */
2449   if (args->nelts == 4)
2450     lock_token = APR_ARRAY_IDX(args, 3, const char *);
2451
2452   SVN_ERR(target_arg_to_dirent(&comment_file_name, comment_file_name, pool));
2453
2454   SVN_ERR(open_repos(&repos, opt_state->repository_path, opt_state, pool));
2455   fs = svn_repos_fs(repos);
2456
2457   /* Create an access context describing the user. */
2458   SVN_ERR(svn_fs_create_access(&access, username, pool));
2459
2460   /* Attach the access context to the filesystem. */
2461   SVN_ERR(svn_fs_set_access(fs, access));
2462
2463   SVN_ERR(svn_stringbuf_from_file2(&file_contents, comment_file_name, pool));
2464
2465   SVN_ERR(target_arg_to_fspath(&lock_path, lock_path, pool, pool));
2466
2467   if (opt_state->bypass_hooks)
2468     SVN_ERR(svn_fs_lock(&lock, fs, lock_path,
2469                         lock_token,
2470                         file_contents->data, /* comment */
2471                         0,                   /* is_dav_comment */
2472                         0,                   /* no expiration time. */
2473                         SVN_INVALID_REVNUM,
2474                         FALSE, pool));
2475   else
2476     SVN_ERR(svn_repos_fs_lock(&lock, repos, lock_path,
2477                               lock_token,
2478                               file_contents->data, /* comment */
2479                               0,                   /* is_dav_comment */
2480                               0,                   /* no expiration time. */
2481                               SVN_INVALID_REVNUM,
2482                               FALSE, pool));
2483
2484   if (! opt_state->quiet)
2485     SVN_ERR(svn_cmdline_printf(pool, _("'%s' locked by user '%s'.\n"),
2486                                lock_path, username));
2487
2488   return SVN_NO_ERROR;
2489 }
2490
2491 static svn_error_t *
2492 subcommand_lslocks(apr_getopt_t *os, void *baton, apr_pool_t *pool)
2493 {
2494   struct svnadmin_opt_state *opt_state = baton;
2495   apr_array_header_t *targets;
2496   svn_repos_t *repos;
2497   const char *fs_path;
2498   apr_hash_t *locks;
2499   apr_hash_index_t *hi;
2500   apr_pool_t *iterpool = svn_pool_create(pool);
2501
2502   SVN_ERR(svn_opt__args_to_target_array(&targets, os,
2503                                         apr_array_make(pool, 0,
2504                                                        sizeof(const char *)),
2505                                         pool));
2506   if (targets->nelts > 1)
2507     return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, 0,
2508                             _("Too many arguments given"));
2509   if (targets->nelts)
2510     fs_path = APR_ARRAY_IDX(targets, 0, const char *);
2511   else
2512     fs_path = "/";
2513   SVN_ERR(target_arg_to_fspath(&fs_path, fs_path, pool, pool));
2514
2515   SVN_ERR(open_repos(&repos, opt_state->repository_path, opt_state, pool));
2516
2517   /* Fetch all locks on or below the root directory. */
2518   SVN_ERR(svn_repos_fs_get_locks2(&locks, repos, fs_path, svn_depth_infinity,
2519                                   NULL, NULL, pool));
2520
2521   for (hi = apr_hash_first(pool, locks); hi; hi = apr_hash_next(hi))
2522     {
2523       const char *cr_date, *exp_date = "";
2524       const char *path = apr_hash_this_key(hi);
2525       svn_lock_t *lock = apr_hash_this_val(hi);
2526       int comment_lines = 0;
2527
2528       svn_pool_clear(iterpool);
2529
2530       SVN_ERR(check_cancel(NULL));
2531
2532       cr_date = svn_time_to_human_cstring(lock->creation_date, iterpool);
2533
2534       if (lock->expiration_date)
2535         exp_date = svn_time_to_human_cstring(lock->expiration_date, iterpool);
2536
2537       if (lock->comment)
2538         comment_lines = svn_cstring_count_newlines(lock->comment) + 1;
2539
2540       SVN_ERR(svn_cmdline_printf(iterpool, _("Path: %s\n"), path));
2541       SVN_ERR(svn_cmdline_printf(iterpool, _("UUID Token: %s\n"), lock->token));
2542       SVN_ERR(svn_cmdline_printf(iterpool, _("Owner: %s\n"), lock->owner));
2543       SVN_ERR(svn_cmdline_printf(iterpool, _("Created: %s\n"), cr_date));
2544       SVN_ERR(svn_cmdline_printf(iterpool, _("Expires: %s\n"), exp_date));
2545       SVN_ERR(svn_cmdline_printf(iterpool,
2546                                  Q_("Comment (%i line):\n%s\n\n",
2547                                     "Comment (%i lines):\n%s\n\n",
2548                                     comment_lines),
2549                                  comment_lines,
2550                                  lock->comment ? lock->comment : ""));
2551     }
2552
2553   svn_pool_destroy(iterpool);
2554
2555   return SVN_NO_ERROR;
2556 }
2557
2558
2559
2560 static svn_error_t *
2561 subcommand_rmlocks(apr_getopt_t *os, void *baton, apr_pool_t *pool)
2562 {
2563   struct svnadmin_opt_state *opt_state = baton;
2564   svn_repos_t *repos;
2565   svn_fs_t *fs;
2566   svn_fs_access_t *access;
2567   svn_error_t *err;
2568   apr_array_header_t *args;
2569   int i;
2570   const char *username;
2571   apr_pool_t *subpool = svn_pool_create(pool);
2572
2573   SVN_ERR(open_repos(&repos, opt_state->repository_path, opt_state, pool));
2574   fs = svn_repos_fs(repos);
2575
2576   /* svn_fs_unlock() demands that some username be associated with the
2577      filesystem, so just use the UID of the person running 'svnadmin'.*/
2578   username = svn_user_get_name(pool);
2579   if (! username)
2580     username = "administrator";
2581
2582   /* Create an access context describing the current user. */
2583   SVN_ERR(svn_fs_create_access(&access, username, pool));
2584
2585   /* Attach the access context to the filesystem. */
2586   SVN_ERR(svn_fs_set_access(fs, access));
2587
2588   /* Parse out any options. */
2589   SVN_ERR(parse_args(&args, os, -1, -1, pool));
2590
2591   /* Our usage requires at least one FS path. */
2592   if (args->nelts == 0)
2593     return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, 0,
2594                             _("No paths to unlock provided"));
2595
2596   /* All the rest of the arguments are paths from which to remove locks. */
2597   for (i = 0; i < args->nelts; i++)
2598     {
2599       const char *lock_path = APR_ARRAY_IDX(args, i, const char *);
2600       svn_lock_t *lock;
2601
2602       SVN_ERR(target_arg_to_fspath(&lock_path, lock_path, subpool, subpool));
2603
2604       /* Fetch the path's svn_lock_t. */
2605       err = svn_fs_get_lock(&lock, fs, lock_path, subpool);
2606       if (err)
2607         goto move_on;
2608       if (! lock)
2609         {
2610           if (! opt_state->quiet)
2611             SVN_ERR(svn_cmdline_printf(subpool,
2612                                        _("Path '%s' isn't locked.\n"),
2613                                        lock_path));
2614           continue;
2615         }
2616       lock = NULL; /* Don't access LOCK after this point. */
2617
2618       /* Now forcibly destroy the lock. */
2619       err = svn_fs_unlock(fs, lock_path,
2620                           NULL, 1 /* force */, subpool);
2621       if (err)
2622         goto move_on;
2623
2624       if (! opt_state->quiet)
2625         SVN_ERR(svn_cmdline_printf(subpool,
2626                                    _("Removed lock on '%s'.\n"),
2627                                    lock_path));
2628
2629     move_on:
2630       if (err)
2631         {
2632           /* Print the error, but move on to the next lock. */
2633           svn_handle_error2(err, stderr, FALSE /* non-fatal */, "svnadmin: ");
2634           svn_error_clear(err);
2635         }
2636
2637       svn_pool_clear(subpool);
2638     }
2639
2640   svn_pool_destroy(subpool);
2641   return SVN_NO_ERROR;
2642 }
2643
2644
2645 /* This implements `svn_opt_subcommand_t'. */
2646 static svn_error_t *
2647 subcommand_unlock(apr_getopt_t *os, void *baton, apr_pool_t *pool)
2648 {
2649   struct svnadmin_opt_state *opt_state = baton;
2650   svn_repos_t *repos;
2651   svn_fs_t *fs;
2652   svn_fs_access_t *access;
2653   apr_array_header_t *args;
2654   const char *username;
2655   const char *lock_path;
2656   const char *lock_token = NULL;
2657
2658   /* Expect three more arguments: PATH USERNAME TOKEN */
2659   SVN_ERR(parse_args(&args, os, 3, 3, pool));
2660   lock_path = APR_ARRAY_IDX(args, 0, const char *);
2661   username = APR_ARRAY_IDX(args, 1, const char *);
2662   lock_token = APR_ARRAY_IDX(args, 2, const char *);
2663
2664   /* Open the repos/FS, and associate an access context containing
2665      USERNAME. */
2666   SVN_ERR(open_repos(&repos, opt_state->repository_path, opt_state, pool));
2667   fs = svn_repos_fs(repos);
2668   SVN_ERR(svn_fs_create_access(&access, username, pool));
2669   SVN_ERR(svn_fs_set_access(fs, access));
2670
2671   SVN_ERR(target_arg_to_fspath(&lock_path, lock_path, pool, pool));
2672   if (opt_state->bypass_hooks)
2673     SVN_ERR(svn_fs_unlock(fs, lock_path, lock_token,
2674                           FALSE, pool));
2675   else
2676     SVN_ERR(svn_repos_fs_unlock(repos, lock_path, lock_token,
2677                                 FALSE, pool));
2678
2679   if (! opt_state->quiet)
2680     SVN_ERR(svn_cmdline_printf(pool, _("'%s' unlocked by user '%s'.\n"),
2681                                lock_path, username));
2682
2683   return SVN_NO_ERROR;
2684 }
2685
2686
2687 /* This implements `svn_opt_subcommand_t'. */
2688 static svn_error_t *
2689 subcommand_upgrade(apr_getopt_t *os, void *baton, apr_pool_t *pool)
2690 {
2691   svn_error_t *err;
2692   struct svnadmin_opt_state *opt_state = baton;
2693   svn_stream_t *feedback_stream = NULL;
2694
2695   /* Expect no more arguments. */
2696   SVN_ERR(parse_args(NULL, os, 0, 0, pool));
2697
2698   SVN_ERR(svn_stream_for_stdout(&feedback_stream, pool));
2699
2700   /* Restore default signal handlers. */
2701   svn_cmdline__disable_cancellation_handler();
2702
2703   err = svn_repos_upgrade2(opt_state->repository_path, TRUE,
2704                            repos_notify_handler, feedback_stream, pool);
2705   if (err)
2706     {
2707       if (APR_STATUS_IS_EAGAIN(err->apr_err))
2708         {
2709           svn_error_clear(err);
2710           err = SVN_NO_ERROR;
2711           if (! opt_state->wait)
2712             return svn_error_create(SVN_ERR_REPOS_LOCKED, NULL,
2713                                     _("Failed to get exclusive repository "
2714                                       "access; perhaps another process\n"
2715                                       "such as httpd, svnserve or svn "
2716                                       "has it open?"));
2717           SVN_ERR(svn_cmdline_printf(pool,
2718                                      _("Waiting on repository lock; perhaps"
2719                                        " another process has it open?\n")));
2720           SVN_ERR(svn_cmdline_fflush(stdout));
2721           SVN_ERR(svn_repos_upgrade2(opt_state->repository_path, FALSE,
2722                                      repos_notify_handler, feedback_stream,
2723                                      pool));
2724         }
2725       else if (err->apr_err == SVN_ERR_FS_UNSUPPORTED_UPGRADE)
2726         {
2727           return svn_error_quick_wrap(err,
2728                     _("Upgrade of this repository's underlying versioned "
2729                     "filesystem is not supported; consider "
2730                     "dumping and loading the data elsewhere"));
2731         }
2732       else if (err->apr_err == SVN_ERR_REPOS_UNSUPPORTED_UPGRADE)
2733         {
2734           return svn_error_quick_wrap(err,
2735                     _("Upgrade of this repository is not supported; consider "
2736                     "dumping and loading the data elsewhere"));
2737         }
2738     }
2739   SVN_ERR(err);
2740
2741   SVN_ERR(svn_cmdline_printf(pool, _("\nUpgrade completed.\n")));
2742   return SVN_NO_ERROR;
2743 }
2744
2745
2746 /* This implements `svn_opt_subcommand_t'. */
2747 static svn_error_t *
2748 subcommand_delrevprop(apr_getopt_t *os, void *baton, apr_pool_t *pool)
2749 {
2750   struct svnadmin_opt_state *opt_state = baton;
2751   apr_array_header_t *args;
2752   const char *prop_name;
2753
2754   /* Expect one more argument: NAME */
2755   SVN_ERR(parse_args(&args, os, 1, 1, pool));
2756   prop_name = APR_ARRAY_IDX(args, 0, const char *);
2757
2758   if (opt_state->txn_id)
2759     {
2760       if (opt_state->start_revision.kind != svn_opt_revision_unspecified
2761           || opt_state->end_revision.kind != svn_opt_revision_unspecified)
2762         return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
2763                                  _("--revision (-r) and --transaction (-t) "
2764                                    "are mutually exclusive"));
2765
2766       if (opt_state->use_pre_revprop_change_hook
2767           || opt_state->use_post_revprop_change_hook)
2768         return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
2769                                  _("Calling hooks is incompatible with "
2770                                    "--transaction (-t)"));
2771     }
2772   else if (opt_state->start_revision.kind != svn_opt_revision_number)
2773     return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
2774                              _("Missing revision"));
2775   else if (opt_state->end_revision.kind != svn_opt_revision_unspecified)
2776     return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
2777                              _("Only one revision allowed"));
2778
2779   return set_revprop(prop_name, NULL, opt_state, pool);
2780 }
2781
2782
2783 \f
2784 /** Main. **/
2785
2786 /*
2787  * On success, leave *EXIT_CODE untouched and return SVN_NO_ERROR. On error,
2788  * either return an error to be displayed, or set *EXIT_CODE to non-zero and
2789  * return SVN_NO_ERROR.
2790  */
2791 static svn_error_t *
2792 sub_main(int *exit_code, int argc, const char *argv[], apr_pool_t *pool)
2793 {
2794   svn_error_t *err;
2795   apr_status_t apr_err;
2796
2797   const svn_opt_subcommand_desc2_t *subcommand = NULL;
2798   struct svnadmin_opt_state opt_state = { 0 };
2799   apr_getopt_t *os;
2800   int opt_id;
2801   apr_array_header_t *received_opts;
2802   int i;
2803   svn_boolean_t dash_F_arg = FALSE;
2804
2805   received_opts = apr_array_make(pool, SVN_OPT_MAX_OPTIONS, sizeof(int));
2806
2807   /* Check library versions */
2808   SVN_ERR(check_lib_versions());
2809
2810   /* Initialize the FS library. */
2811   SVN_ERR(svn_fs_initialize(pool));
2812
2813   if (argc <= 1)
2814     {
2815       SVN_ERR(subcommand_help(NULL, NULL, pool));
2816       *exit_code = EXIT_FAILURE;
2817       return SVN_NO_ERROR;
2818     }
2819
2820   /* Initialize opt_state. */
2821   opt_state.start_revision.kind = svn_opt_revision_unspecified;
2822   opt_state.end_revision.kind = svn_opt_revision_unspecified;
2823   opt_state.memory_cache_size = svn_cache_config_get()->cache_size;
2824
2825   /* Parse options. */
2826   SVN_ERR(svn_cmdline__getopt_init(&os, argc, argv, pool));
2827
2828   os->interleave = 1;
2829
2830   while (1)
2831     {
2832       const char *opt_arg;
2833       const char *utf8_opt_arg;
2834
2835       /* Parse the next option. */
2836       apr_err = apr_getopt_long(os, options_table, &opt_id, &opt_arg);
2837       if (APR_STATUS_IS_EOF(apr_err))
2838         break;
2839       else if (apr_err)
2840         {
2841           SVN_ERR(subcommand_help(NULL, NULL, pool));
2842           *exit_code = EXIT_FAILURE;
2843           return SVN_NO_ERROR;
2844         }
2845
2846       /* Stash the option code in an array before parsing it. */
2847       APR_ARRAY_PUSH(received_opts, int) = opt_id;
2848
2849       switch (opt_id) {
2850       case 'r':
2851         {
2852           if (opt_state.start_revision.kind != svn_opt_revision_unspecified)
2853             {
2854               return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
2855                         _("Multiple revision arguments encountered; "
2856                           "try '-r N:M' instead of '-r N -r M'"));
2857             }
2858           if (svn_opt_parse_revision(&(opt_state.start_revision),
2859                                      &(opt_state.end_revision),
2860                                      opt_arg, pool) != 0)
2861             {
2862               SVN_ERR(svn_utf_cstring_to_utf8(&utf8_opt_arg, opt_arg, pool));
2863
2864               return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
2865                         _("Syntax error in revision argument '%s'"),
2866                         utf8_opt_arg);
2867             }
2868         }
2869         break;
2870       case 't':
2871         opt_state.txn_id = opt_arg;
2872         break;
2873
2874       case 'q':
2875         opt_state.quiet = TRUE;
2876         break;
2877       case 'h':
2878       case '?':
2879         opt_state.help = TRUE;
2880         break;
2881       case 'M':
2882         {
2883           apr_uint64_t sz_val;
2884           SVN_ERR(svn_cstring_atoui64(&sz_val, opt_arg));
2885
2886           opt_state.memory_cache_size = 0x100000 * sz_val;
2887         }
2888         break;
2889       case 'F':
2890         SVN_ERR(svn_utf_cstring_to_utf8(&(opt_state.file), opt_arg, pool));
2891         dash_F_arg = TRUE;
2892         break;
2893       case svnadmin__version:
2894         opt_state.version = TRUE;
2895         break;
2896       case svnadmin__incremental:
2897         opt_state.incremental = TRUE;
2898         break;
2899       case svnadmin__deltas:
2900         opt_state.use_deltas = TRUE;
2901         break;
2902       case svnadmin__ignore_uuid:
2903         opt_state.uuid_action = svn_repos_load_uuid_ignore;
2904         break;
2905       case svnadmin__force_uuid:
2906         opt_state.uuid_action = svn_repos_load_uuid_force;
2907         break;
2908       case svnadmin__pre_1_4_compatible:
2909         opt_state.compatible_version = apr_pcalloc(pool, sizeof(svn_version_t));
2910         opt_state.compatible_version->major = 1;
2911         opt_state.compatible_version->minor = 3;
2912         break;
2913       case svnadmin__pre_1_5_compatible:
2914         opt_state.compatible_version = apr_pcalloc(pool, sizeof(svn_version_t));
2915         opt_state.compatible_version->major = 1;
2916         opt_state.compatible_version->minor = 4;
2917         break;
2918       case svnadmin__pre_1_6_compatible:
2919         opt_state.compatible_version = apr_pcalloc(pool, sizeof(svn_version_t));
2920         opt_state.compatible_version->major = 1;
2921         opt_state.compatible_version->minor = 5;
2922         break;
2923       case svnadmin__compatible_version:
2924         {
2925           svn_version_t latest = { SVN_VER_MAJOR, SVN_VER_MINOR,
2926                                    SVN_VER_PATCH, NULL };
2927           svn_version_t *compatible_version;
2928
2929           /* Parse the version string which carries our target
2930              compatibility. */
2931           SVN_ERR(svn_version__parse_version_string(&compatible_version,
2932                                                         opt_arg, pool));
2933
2934           /* We can't create repository with a version older than 1.0.0.  */
2935           if (! svn_version__at_least(compatible_version, 1, 0, 0))
2936             {
2937               return svn_error_createf(SVN_ERR_UNSUPPORTED_FEATURE, NULL,
2938                                        _("Cannot create pre-1.0-compatible "
2939                                          "repositories"));
2940             }
2941
2942           /* We can't create repository with a version newer than what
2943              the running version of Subversion supports. */
2944           if (! svn_version__at_least(&latest,
2945                                       compatible_version->major,
2946                                       compatible_version->minor,
2947                                       compatible_version->patch))
2948             {
2949               return svn_error_createf(SVN_ERR_UNSUPPORTED_FEATURE, NULL,
2950                                        _("Cannot guarantee compatibility "
2951                                          "beyond the current running version "
2952                                          "(%s)"),
2953                                        SVN_VER_NUM);
2954             }
2955
2956           opt_state.compatible_version = compatible_version;
2957         }
2958         break;
2959       case svnadmin__keep_going:
2960         opt_state.keep_going = TRUE;
2961         break;
2962       case svnadmin__check_normalization:
2963         opt_state.check_normalization = TRUE;
2964         break;
2965       case svnadmin__metadata_only:
2966         opt_state.metadata_only = TRUE;
2967         break;
2968       case svnadmin__fs_type:
2969         SVN_ERR(svn_utf_cstring_to_utf8(&opt_state.fs_type, opt_arg, pool));
2970         break;
2971       case svnadmin__parent_dir:
2972         SVN_ERR(svn_utf_cstring_to_utf8(&opt_state.parent_dir, opt_arg,
2973                                             pool));
2974         opt_state.parent_dir
2975           = svn_dirent_internal_style(opt_state.parent_dir, pool);
2976         break;
2977       case svnadmin__use_pre_commit_hook:
2978         opt_state.use_pre_commit_hook = TRUE;
2979         break;
2980       case svnadmin__use_post_commit_hook:
2981         opt_state.use_post_commit_hook = TRUE;
2982         break;
2983       case svnadmin__use_pre_revprop_change_hook:
2984         opt_state.use_pre_revprop_change_hook = TRUE;
2985         break;
2986       case svnadmin__use_post_revprop_change_hook:
2987         opt_state.use_post_revprop_change_hook = TRUE;
2988         break;
2989       case svnadmin__bdb_txn_nosync:
2990         opt_state.bdb_txn_nosync = TRUE;
2991         break;
2992       case svnadmin__bdb_log_keep:
2993         opt_state.bdb_log_keep = TRUE;
2994         break;
2995       case svnadmin__bypass_hooks:
2996         opt_state.bypass_hooks = TRUE;
2997         break;
2998       case svnadmin__bypass_prop_validation:
2999         opt_state.bypass_prop_validation = TRUE;
3000         break;
3001       case svnadmin__ignore_dates:
3002         opt_state.ignore_dates = TRUE;
3003         break;
3004       case svnadmin__clean_logs:
3005         opt_state.clean_logs = TRUE;
3006         break;
3007       case svnadmin__config_dir:
3008         SVN_ERR(svn_utf_cstring_to_utf8(&utf8_opt_arg, opt_arg, pool));
3009         opt_state.config_dir =
3010             apr_pstrdup(pool, svn_dirent_canonicalize(utf8_opt_arg, pool));
3011         break;
3012       case svnadmin__wait:
3013         opt_state.wait = TRUE;
3014         break;
3015       case svnadmin__no_flush_to_disk:
3016         opt_state.no_flush_to_disk = TRUE;
3017         break;
3018       case svnadmin__normalize_props:
3019         opt_state.normalize_props = TRUE;
3020         break;
3021       case svnadmin__exclude:
3022         SVN_ERR(svn_utf_cstring_to_utf8(&utf8_opt_arg, opt_arg, pool));
3023
3024         if (! opt_state.exclude)
3025           opt_state.exclude = apr_array_make(pool, 1, sizeof(const char *));
3026         APR_ARRAY_PUSH(opt_state.exclude, const char *) = utf8_opt_arg;
3027         break;
3028       case svnadmin__include:
3029         SVN_ERR(svn_utf_cstring_to_utf8(&utf8_opt_arg, opt_arg, pool));
3030
3031         if (! opt_state.include)
3032           opt_state.include = apr_array_make(pool, 1, sizeof(const char *));
3033         APR_ARRAY_PUSH(opt_state.include, const char *) = utf8_opt_arg;
3034         break;
3035       case svnadmin__glob:
3036         opt_state.glob = TRUE;
3037         break;
3038       default:
3039         {
3040           SVN_ERR(subcommand_help(NULL, NULL, pool));
3041           *exit_code = EXIT_FAILURE;
3042           return SVN_NO_ERROR;
3043         }
3044       }  /* close `switch' */
3045     }  /* close `while' */
3046
3047   /* If the user asked for help, then the rest of the arguments are
3048      the names of subcommands to get help on (if any), or else they're
3049      just typos/mistakes.  Whatever the case, the subcommand to
3050      actually run is subcommand_help(). */
3051   if (opt_state.help)
3052     subcommand = svn_opt_get_canonical_subcommand2(cmd_table, "help");
3053
3054   /* If we're not running the `help' subcommand, then look for a
3055      subcommand in the first argument. */
3056   if (subcommand == NULL)
3057     {
3058       if (os->ind >= os->argc)
3059         {
3060           if (opt_state.version)
3061             {
3062               /* Use the "help" subcommand to handle the "--version" option. */
3063               static const svn_opt_subcommand_desc2_t pseudo_cmd =
3064                 { "--version", subcommand_help, {0}, "",
3065                   {svnadmin__version,  /* must accept its own option */
3066                    'q',  /* --quiet */
3067                   } };
3068
3069               subcommand = &pseudo_cmd;
3070             }
3071           else
3072             {
3073               svn_error_clear(svn_cmdline_fprintf(stderr, pool,
3074                                         _("subcommand argument required\n")));
3075               SVN_ERR(subcommand_help(NULL, NULL, pool));
3076               *exit_code = EXIT_FAILURE;
3077               return SVN_NO_ERROR;
3078             }
3079         }
3080       else
3081         {
3082           const char *first_arg;
3083
3084           SVN_ERR(svn_utf_cstring_to_utf8(&first_arg, os->argv[os->ind++],
3085                                           pool));
3086           subcommand = svn_opt_get_canonical_subcommand2(cmd_table, first_arg);
3087           if (subcommand == NULL)
3088             {
3089               svn_error_clear(
3090                 svn_cmdline_fprintf(stderr, pool,
3091                                     _("Unknown subcommand: '%s'\n"),
3092                                     first_arg));
3093               SVN_ERR(subcommand_help(NULL, NULL, pool));
3094               *exit_code = EXIT_FAILURE;
3095               return SVN_NO_ERROR;
3096             }
3097         }
3098     }
3099
3100   /* Every subcommand except `help' and `freeze' with '-F' require a
3101      second argument -- the repository path.  Parse it out here and
3102      store it in opt_state. */
3103   if (!(subcommand->cmd_func == subcommand_help
3104         || (subcommand->cmd_func == subcommand_freeze && dash_F_arg)))
3105     {
3106       const char *repos_path = NULL;
3107
3108       if (os->ind >= os->argc)
3109         {
3110           return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
3111                                   _("Repository argument required"));
3112         }
3113
3114       SVN_ERR(svn_utf_cstring_to_utf8(&repos_path, os->argv[os->ind++], pool));
3115
3116       if (svn_path_is_url(repos_path))
3117         {
3118           return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
3119                                    _("'%s' is a URL when it should be a "
3120                                      "local path"), repos_path);
3121         }
3122
3123       opt_state.repository_path = svn_dirent_internal_style(repos_path, pool);
3124     }
3125
3126   /* Check that the subcommand wasn't passed any inappropriate options. */
3127   for (i = 0; i < received_opts->nelts; i++)
3128     {
3129       opt_id = APR_ARRAY_IDX(received_opts, i, int);
3130
3131       /* All commands implicitly accept --help, so just skip over this
3132          when we see it. Note that we don't want to include this option
3133          in their "accepted options" list because it would be awfully
3134          redundant to display it in every commands' help text. */
3135       if (opt_id == 'h' || opt_id == '?')
3136         continue;
3137
3138       if (! svn_opt_subcommand_takes_option3(subcommand, opt_id, NULL))
3139         {
3140           const char *optstr;
3141           const apr_getopt_option_t *badopt =
3142             svn_opt_get_option_from_code2(opt_id, options_table, subcommand,
3143                                           pool);
3144           svn_opt_format_option(&optstr, badopt, FALSE, pool);
3145           if (subcommand->name[0] == '-')
3146             SVN_ERR(subcommand_help(NULL, NULL, pool));
3147           else
3148             svn_error_clear(svn_cmdline_fprintf(stderr, pool
3149                             , _("Subcommand '%s' doesn't accept option '%s'\n"
3150                                 "Type 'svnadmin help %s' for usage.\n"),
3151                 subcommand->name, optstr, subcommand->name));
3152           *exit_code = EXIT_FAILURE;
3153           return SVN_NO_ERROR;
3154         }
3155     }
3156
3157   check_cancel = svn_cmdline__setup_cancellation_handler();
3158
3159   /* Configure FSFS caches for maximum efficiency with svnadmin.
3160    * Also, apply the respective command line parameters, if given. */
3161   {
3162     svn_cache_config_t settings = *svn_cache_config_get();
3163
3164     settings.cache_size = opt_state.memory_cache_size;
3165     settings.single_threaded = TRUE;
3166
3167     svn_cache_config_set(&settings);
3168   }
3169
3170   /* Run the subcommand. */
3171   err = (*subcommand->cmd_func)(os, &opt_state, pool);
3172   if (err)
3173     {
3174       /* For argument-related problems, suggest using the 'help'
3175          subcommand. */
3176       if (err->apr_err == SVN_ERR_CL_INSUFFICIENT_ARGS
3177           || err->apr_err == SVN_ERR_CL_ARG_PARSING_ERROR)
3178         {
3179           err = svn_error_quick_wrap(err,
3180                                      _("Try 'svnadmin help' for more info"));
3181         }
3182       return err;
3183     }
3184
3185   return SVN_NO_ERROR;
3186 }
3187
3188 int
3189 main(int argc, const char *argv[])
3190 {
3191   apr_pool_t *pool;
3192   int exit_code = EXIT_SUCCESS;
3193   svn_error_t *err;
3194
3195   /* Initialize the app. */
3196   if (svn_cmdline_init("svnadmin", stderr) != EXIT_SUCCESS)
3197     return EXIT_FAILURE;
3198
3199   /* Create our top-level pool.  Use a separate mutexless allocator,
3200    * given this application is single threaded.
3201    */
3202   pool = apr_allocator_owner_get(svn_pool_create_allocator(FALSE));
3203
3204   err = sub_main(&exit_code, argc, argv, pool);
3205
3206   /* Flush stdout and report if it fails. It would be flushed on exit anyway
3207      but this makes sure that output is not silently lost if it fails. */
3208   err = svn_error_compose_create(err, svn_cmdline_fflush(stdout));
3209
3210   if (err)
3211     {
3212       exit_code = EXIT_FAILURE;
3213       svn_cmdline_handle_exit_error(err, NULL, "svnadmin: ");
3214     }
3215
3216   svn_pool_destroy(pool);
3217
3218   svn_cmdline__cancellation_exit();
3219
3220   return exit_code;
3221 }