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