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