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
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
65static svn_cancel_func_t check_cancel = NULL;
66
67/* Custom filesystem warning function. */
68static void
69warning_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 */
79static svn_error_t *
80check_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
97/** Subcommands. **/
98
99static 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
130enum 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 */
169static 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 */
308static 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. */
640struct 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).  */
683static svn_error_t *
684open_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. */
716static svn_error_t *
717get_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. */
742static svn_error_t *
743target_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. */
755static svn_error_t *
756target_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.  */
777static svn_error_t *
778parse_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. */
814static svn_error_t *
815crashtest_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'. */
825static svn_error_t *
826subcommand_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'. */
846static svn_error_t *
847subcommand_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'. */
936static svn_error_t *
937subcommand_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'. */
988struct 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 *. */
995static apr_status_t
996err_cleanup(void *data)
997{
998  svn_error_t *err = data;
999
1000  svn_error_clear(err);
1001
1002  return APR_SUCCESS;
1003}
1004
1005struct 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(). */
1019static svn_error_t *
1020repos_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. */
1062static void
1063repos_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(). */
1291struct 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. */
1305static 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. */
1316static svn_stream_t *
1317recode_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. */
1332static svn_error_t *
1333get_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(). */
1377static svn_boolean_t
1378ary_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(). */
1399struct 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. */
1407static svn_error_t *
1408dump_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'. */
1425static svn_error_t *
1426subcommand_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'. */
1492static svn_error_t *
1493subcommand_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
1534struct freeze_baton_t {
1535  const char *command;
1536  const char **args;
1537  int status;
1538};
1539
1540/* Implements svn_repos_freeze_func_t */
1541static svn_error_t *
1542freeze_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
1566static svn_error_t *
1567subcommand_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'. */
1614static svn_error_t *
1615subcommand_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. */
1648static svn_error_t *
1649optrev_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. */
1673static svn_error_t *
1674get_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'. */
1705static svn_error_t *
1706subcommand_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
1764static svn_error_t *
1765subcommand_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'. */
1823static svn_error_t *
1824subcommand_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'. */
1876static svn_error_t *
1877subcommand_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'. */
1933static svn_error_t *
1934list_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'. */
1967static svn_error_t *
1968subcommand_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'. */
1976static svn_error_t *
1977subcommand_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'. */
1988static svn_error_t *
1989subcommand_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.  */
2052static svn_error_t *
2053set_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'. */
2101static svn_error_t *
2102subcommand_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'. */
2140static svn_error_t *
2141subcommand_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'. */
2161static svn_error_t *
2162subcommand_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'. */
2192static svn_error_t *
2193subcommand_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'. */
2215static svn_error_t *
2216subcommand_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'. */
2358svn_error_t *
2359subcommand_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
2381svn_error_t *
2382subcommand_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'. */
2519static svn_error_t *
2520subcommand_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
2583static svn_error_t *
2584subcommand_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
2652static svn_error_t *
2653subcommand_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'. */
2738static svn_error_t *
2739subcommand_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'. */
2780static svn_error_t *
2781subcommand_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'. */
2839static svn_error_t *
2840subcommand_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 */
2883static svn_error_t *
2884revision_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'. */
2911svn_error_t *
2912subcommand_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
2937static void
2938build_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
2947static svn_error_t *
2948build_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'. */
2996static svn_error_t *
2997subcommand_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
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 */
3046static svn_error_t *
3047sub_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
3443int
3444main(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}
3477