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