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