]> CyberLeo.Net >> Repos - FreeBSD/releng/10.2.git/blob - contrib/subversion/subversion/svnadmin/svnadmin.c
- Copy stable/10@285827 to releng/10.2 in preparation for 10.2-RC1
[FreeBSD/releng/10.2.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_list2(&my_version, checklist, svn_ver_equal);
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
760 /* Implementation of svn_repos_notify_func_t to wrap the output to a
761    response stream for svn_repos_dump_fs2() and svn_repos_verify_fs() */
762 static void
763 repos_notify_handler(void *baton,
764                      const svn_repos_notify_t *notify,
765                      apr_pool_t *scratch_pool)
766 {
767   svn_stream_t *feedback_stream = baton;
768   apr_size_t len;
769
770   switch (notify->action)
771   {
772     case svn_repos_notify_warning:
773       svn_error_clear(svn_stream_printf(feedback_stream, scratch_pool,
774                                         "WARNING 0x%04x: %s\n", notify->warning,
775                                         notify->warning_str));
776       return;
777
778     case svn_repos_notify_dump_rev_end:
779       svn_error_clear(svn_stream_printf(feedback_stream, scratch_pool,
780                                         _("* Dumped revision %ld.\n"),
781                                         notify->revision));
782       return;
783
784     case svn_repos_notify_verify_rev_end:
785       svn_error_clear(svn_stream_printf(feedback_stream, scratch_pool,
786                                         _("* Verified revision %ld.\n"),
787                                         notify->revision));
788       return;
789
790     case svn_repos_notify_verify_rev_structure:
791       if (notify->revision == SVN_INVALID_REVNUM)
792         svn_error_clear(svn_stream_printf(feedback_stream, scratch_pool,
793                                 _("* Verifying repository metadata ...\n")));
794       else
795         svn_error_clear(svn_stream_printf(feedback_stream, scratch_pool,
796                         _("* Verifying metadata at revision %ld ...\n"),
797                         notify->revision));
798       return;
799
800     case svn_repos_notify_pack_shard_start:
801       {
802         const char *shardstr = apr_psprintf(scratch_pool,
803                                             "%" APR_INT64_T_FMT,
804                                             notify->shard);
805         svn_error_clear(svn_stream_printf(feedback_stream, scratch_pool,
806                                           _("Packing revisions in shard %s..."),
807                                           shardstr));
808       }
809       return;
810
811     case svn_repos_notify_pack_shard_end:
812       svn_error_clear(svn_stream_puts(feedback_stream, _("done.\n")));
813       return;
814
815     case svn_repos_notify_pack_shard_start_revprop:
816       {
817         const char *shardstr = apr_psprintf(scratch_pool,
818                                             "%" APR_INT64_T_FMT,
819                                             notify->shard);
820         svn_error_clear(svn_stream_printf(feedback_stream, scratch_pool,
821                                           _("Packing revprops in shard %s..."),
822                                           shardstr));
823       }
824       return;
825
826     case svn_repos_notify_pack_shard_end_revprop:
827       svn_error_clear(svn_stream_puts(feedback_stream, _("done.\n")));
828       return;
829
830     case svn_repos_notify_load_txn_committed:
831       if (notify->old_revision == SVN_INVALID_REVNUM)
832         {
833           svn_error_clear(svn_stream_printf(feedback_stream, scratch_pool,
834                             _("\n------- Committed revision %ld >>>\n\n"),
835                             notify->new_revision));
836         }
837       else
838         {
839           svn_error_clear(svn_stream_printf(feedback_stream, scratch_pool,
840                             _("\n------- Committed new rev %ld"
841                               " (loaded from original rev %ld"
842                               ") >>>\n\n"), notify->new_revision,
843                               notify->old_revision));
844         }
845       return;
846
847     case svn_repos_notify_load_node_start:
848       {
849         switch (notify->node_action)
850         {
851           case svn_node_action_change:
852             svn_error_clear(svn_stream_printf(feedback_stream, scratch_pool,
853                                   _("     * editing path : %s ..."),
854                                   notify->path));
855             break;
856
857           case svn_node_action_delete:
858             svn_error_clear(svn_stream_printf(feedback_stream, scratch_pool,
859                                   _("     * deleting path : %s ..."),
860                                   notify->path));
861             break;
862
863           case svn_node_action_add:
864             svn_error_clear(svn_stream_printf(feedback_stream, scratch_pool,
865                                   _("     * adding path : %s ..."),
866                                   notify->path));
867             break;
868
869           case svn_node_action_replace:
870             svn_error_clear(svn_stream_printf(feedback_stream, scratch_pool,
871                                   _("     * replacing path : %s ..."),
872                                   notify->path));
873             break;
874
875         }
876       }
877       return;
878
879     case svn_repos_notify_load_node_done:
880       svn_error_clear(svn_stream_printf(feedback_stream, scratch_pool,
881                                         "%s", _(" done.\n")));
882       return;
883
884     case svn_repos_notify_load_copied_node:
885       len = 9;
886       svn_error_clear(svn_stream_write(feedback_stream, "COPIED...", &len));
887       return;
888
889     case svn_repos_notify_load_txn_start:
890       svn_error_clear(svn_stream_printf(feedback_stream, scratch_pool,
891                                 _("<<< Started new transaction, based on "
892                                   "original revision %ld\n"),
893                                 notify->old_revision));
894       return;
895
896     case svn_repos_notify_load_skipped_rev:
897       svn_error_clear(svn_stream_printf(feedback_stream, scratch_pool,
898                                 _("<<< Skipped original revision %ld\n"),
899                                 notify->old_revision));
900       return;
901
902     case svn_repos_notify_load_normalized_mergeinfo:
903       svn_error_clear(svn_stream_printf(feedback_stream, scratch_pool,
904                                 _(" removing '\\r' from %s ..."),
905                                 SVN_PROP_MERGEINFO));
906       return;
907
908     case svn_repos_notify_mutex_acquired:
909       /* Enable cancellation signal handlers. */
910       setup_cancellation_signals(signal_handler);
911       return;
912
913     case svn_repos_notify_recover_start:
914       svn_error_clear(svn_stream_printf(feedback_stream, scratch_pool,
915                              _("Repository lock acquired.\n"
916                                "Please wait; recovering the"
917                                " repository may take some time...\n")));
918       return;
919
920     case svn_repos_notify_upgrade_start:
921       svn_error_clear(svn_stream_puts(feedback_stream,
922                              _("Repository lock acquired.\n"
923                                "Please wait; upgrading the"
924                                " repository may take some time...\n")));
925       return;
926
927     default:
928       return;
929   }
930 }
931
932
933 /* Baton for recode_write(). */
934 struct recode_write_baton
935 {
936   apr_pool_t *pool;
937   FILE *out;
938 };
939
940 /* This implements the 'svn_write_fn_t' interface.
941
942    Write DATA to ((struct recode_write_baton *) BATON)->out, in the
943    console encoding, using svn_cmdline_fprintf().  DATA is a
944    UTF8-encoded C string, therefore ignore LEN.
945
946    ### This recoding mechanism might want to be abstracted into
947    ### svn_io.h or svn_cmdline.h, if it proves useful elsewhere. */
948 static svn_error_t *recode_write(void *baton,
949                                  const char *data,
950                                  apr_size_t *len)
951 {
952   struct recode_write_baton *rwb = baton;
953   svn_pool_clear(rwb->pool);
954   return svn_cmdline_fputs(data, rwb->out, rwb->pool);
955 }
956
957 /* Create a stream, to write to STD_STREAM, that uses recode_write()
958    to perform UTF-8 to console encoding translation. */
959 static svn_stream_t *
960 recode_stream_create(FILE *std_stream, apr_pool_t *pool)
961 {
962   struct recode_write_baton *std_stream_rwb =
963     apr_palloc(pool, sizeof(struct recode_write_baton));
964
965   svn_stream_t *rw_stream = svn_stream_create(std_stream_rwb, pool);
966   std_stream_rwb->pool = svn_pool_create(pool);
967   std_stream_rwb->out = std_stream;
968   svn_stream_set_write(rw_stream, recode_write);
969   return rw_stream;
970 }
971
972
973 /* This implements `svn_opt_subcommand_t'. */
974 static svn_error_t *
975 subcommand_dump(apr_getopt_t *os, void *baton, apr_pool_t *pool)
976 {
977   struct svnadmin_opt_state *opt_state = baton;
978   svn_repos_t *repos;
979   svn_fs_t *fs;
980   svn_stream_t *stdout_stream;
981   svn_revnum_t lower = SVN_INVALID_REVNUM, upper = SVN_INVALID_REVNUM;
982   svn_revnum_t youngest;
983   svn_stream_t *progress_stream = NULL;
984
985   /* Expect no more arguments. */
986   SVN_ERR(parse_args(NULL, os, 0, 0, pool));
987
988   SVN_ERR(open_repos(&repos, opt_state->repository_path, pool));
989   fs = svn_repos_fs(repos);
990   SVN_ERR(svn_fs_youngest_rev(&youngest, fs, pool));
991
992   /* Find the revision numbers at which to start and end. */
993   SVN_ERR(get_revnum(&lower, &opt_state->start_revision,
994                      youngest, repos, pool));
995   SVN_ERR(get_revnum(&upper, &opt_state->end_revision,
996                      youngest, repos, pool));
997
998   /* Fill in implied revisions if necessary. */
999   if (lower == SVN_INVALID_REVNUM)
1000     {
1001       lower = 0;
1002       upper = youngest;
1003     }
1004   else if (upper == SVN_INVALID_REVNUM)
1005     {
1006       upper = lower;
1007     }
1008
1009   if (lower > upper)
1010     return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
1011        _("First revision cannot be higher than second"));
1012
1013   SVN_ERR(svn_stream_for_stdout(&stdout_stream, pool));
1014
1015   /* Progress feedback goes to STDERR, unless they asked to suppress it. */
1016   if (! opt_state->quiet)
1017     progress_stream = recode_stream_create(stderr, pool);
1018
1019   SVN_ERR(svn_repos_dump_fs3(repos, stdout_stream, lower, upper,
1020                              opt_state->incremental, opt_state->use_deltas,
1021                              !opt_state->quiet ? repos_notify_handler : NULL,
1022                              progress_stream, check_cancel, NULL, pool));
1023
1024   return SVN_NO_ERROR;
1025 }
1026
1027 struct freeze_baton_t {
1028   const char *command;
1029   const char **args;
1030   int status;
1031 };
1032
1033 /* Implements svn_repos_freeze_func_t */
1034 static svn_error_t *
1035 freeze_body(void *baton,
1036             apr_pool_t *pool)
1037 {
1038   struct freeze_baton_t *b = baton;
1039   apr_status_t apr_err;
1040   apr_file_t *infile, *outfile, *errfile;
1041
1042   apr_err = apr_file_open_stdin(&infile, pool);
1043   if (apr_err)
1044     return svn_error_wrap_apr(apr_err, "Can't open stdin");
1045   apr_err = apr_file_open_stdout(&outfile, pool);
1046   if (apr_err)
1047     return svn_error_wrap_apr(apr_err, "Can't open stdout");
1048   apr_err = apr_file_open_stderr(&errfile, pool);
1049   if (apr_err)
1050     return svn_error_wrap_apr(apr_err, "Can't open stderr");
1051
1052   SVN_ERR(svn_io_run_cmd(NULL, b->command, b->args, &b->status,
1053                          NULL, TRUE,
1054                          infile, outfile, errfile, pool));
1055
1056   return SVN_NO_ERROR;
1057 }
1058
1059 static svn_error_t *
1060 subcommand_freeze(apr_getopt_t *os, void *baton, apr_pool_t *pool)
1061 {
1062   struct svnadmin_opt_state *opt_state = baton;
1063   apr_array_header_t *paths;
1064   apr_array_header_t *args;
1065   int i;
1066   struct freeze_baton_t b;
1067
1068   SVN_ERR(svn_opt_parse_all_args(&args, os, pool));
1069
1070   if (!args->nelts)
1071     return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, 0,
1072                             _("No program provided"));
1073
1074   if (!opt_state->filedata)
1075     {
1076       /* One repository on the command line. */
1077       paths = apr_array_make(pool, 1, sizeof(const char *));
1078       APR_ARRAY_PUSH(paths, const char *) = opt_state->repository_path;
1079     }
1080   else
1081     {
1082       /* All repositories in filedata. */
1083       paths = svn_cstring_split(opt_state->filedata->data, "\n", FALSE, pool);
1084     }
1085
1086   b.command = APR_ARRAY_IDX(args, 0, const char *);
1087   b.args = apr_palloc(pool, sizeof(char *) * args->nelts + 1);
1088   for (i = 0; i < args->nelts; ++i)
1089     b.args[i] = APR_ARRAY_IDX(args, i, const char *);
1090   b.args[args->nelts] = NULL;
1091
1092   SVN_ERR(svn_repos_freeze(paths, freeze_body, &b, pool));
1093
1094   /* Make any non-zero status visible to the user. */
1095   if (b.status)
1096     exit(b.status);
1097
1098   return SVN_NO_ERROR;
1099 }
1100
1101
1102 /* This implements `svn_opt_subcommand_t'. */
1103 static svn_error_t *
1104 subcommand_help(apr_getopt_t *os, void *baton, apr_pool_t *pool)
1105 {
1106   struct svnadmin_opt_state *opt_state = baton;
1107   const char *header =
1108     _("general usage: svnadmin SUBCOMMAND REPOS_PATH  [ARGS & OPTIONS ...]\n"
1109       "Type 'svnadmin help <subcommand>' for help on a specific subcommand.\n"
1110       "Type 'svnadmin --version' to see the program version and FS modules.\n"
1111       "\n"
1112       "Available subcommands:\n");
1113
1114   const char *fs_desc_start
1115     = _("The following repository back-end (FS) modules are available:\n\n");
1116
1117   svn_stringbuf_t *version_footer;
1118
1119   version_footer = svn_stringbuf_create(fs_desc_start, pool);
1120   SVN_ERR(svn_fs_print_modules(version_footer, pool));
1121
1122   SVN_ERR(svn_opt_print_help4(os, "svnadmin",
1123                               opt_state ? opt_state->version : FALSE,
1124                               opt_state ? opt_state->quiet : FALSE,
1125                               /*###opt_state ? opt_state->verbose :*/ FALSE,
1126                               version_footer->data,
1127                               header, cmd_table, options_table, NULL, NULL,
1128                               pool));
1129
1130   return SVN_NO_ERROR;
1131 }
1132
1133
1134 /* Set *REVNUM to the revision number of a numeric REV, or to
1135    SVN_INVALID_REVNUM if REV is unspecified. */
1136 static svn_error_t *
1137 optrev_to_revnum(svn_revnum_t *revnum, const svn_opt_revision_t *opt_rev)
1138 {
1139   if (opt_rev->kind == svn_opt_revision_number)
1140     {
1141       *revnum = opt_rev->value.number;
1142       if (! SVN_IS_VALID_REVNUM(*revnum))
1143         return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
1144                                  _("Invalid revision number (%ld) specified"),
1145                                  *revnum);
1146     }
1147   else if (opt_rev->kind == svn_opt_revision_unspecified)
1148     {
1149       *revnum = SVN_INVALID_REVNUM;
1150     }
1151   else
1152     {
1153       return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
1154                               _("Non-numeric revision specified"));
1155     }
1156   return SVN_NO_ERROR;
1157 }
1158
1159
1160 /* This implements `svn_opt_subcommand_t'. */
1161 static svn_error_t *
1162 subcommand_load(apr_getopt_t *os, void *baton, apr_pool_t *pool)
1163 {
1164   svn_error_t *err;
1165   struct svnadmin_opt_state *opt_state = baton;
1166   svn_repos_t *repos;
1167   svn_revnum_t lower = SVN_INVALID_REVNUM, upper = SVN_INVALID_REVNUM;
1168   svn_stream_t *stdin_stream, *stdout_stream = NULL;
1169
1170   /* Expect no more arguments. */
1171   SVN_ERR(parse_args(NULL, os, 0, 0, pool));
1172
1173   /* Find the revision numbers at which to start and end.  We only
1174      support a limited set of revision kinds: number and unspecified. */
1175   SVN_ERR(optrev_to_revnum(&lower, &opt_state->start_revision));
1176   SVN_ERR(optrev_to_revnum(&upper, &opt_state->end_revision));
1177
1178   /* Fill in implied revisions if necessary. */
1179   if ((upper == SVN_INVALID_REVNUM) && (lower != SVN_INVALID_REVNUM))
1180     {
1181       upper = lower;
1182     }
1183   else if ((upper != SVN_INVALID_REVNUM) && (lower == SVN_INVALID_REVNUM))
1184     {
1185       lower = upper;
1186     }
1187
1188   /* Ensure correct range ordering. */
1189   if (lower > upper)
1190     {
1191       return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
1192                               _("First revision cannot be higher than second"));
1193     }
1194
1195   SVN_ERR(open_repos(&repos, opt_state->repository_path, pool));
1196
1197   /* Read the stream from STDIN.  Users can redirect a file. */
1198   SVN_ERR(svn_stream_for_stdin(&stdin_stream, pool));
1199
1200   /* Progress feedback goes to STDOUT, unless they asked to suppress it. */
1201   if (! opt_state->quiet)
1202     stdout_stream = recode_stream_create(stdout, pool);
1203
1204   err = svn_repos_load_fs4(repos, stdin_stream, lower, upper,
1205                            opt_state->uuid_action, opt_state->parent_dir,
1206                            opt_state->use_pre_commit_hook,
1207                            opt_state->use_post_commit_hook,
1208                            !opt_state->bypass_prop_validation,
1209                            opt_state->quiet ? NULL : repos_notify_handler,
1210                            stdout_stream, check_cancel, NULL, pool);
1211   if (err && err->apr_err == SVN_ERR_BAD_PROPERTY_VALUE)
1212     return svn_error_quick_wrap(err,
1213                                 _("Invalid property value found in "
1214                                   "dumpstream; consider repairing the source "
1215                                   "or using --bypass-prop-validation while "
1216                                   "loading."));
1217   return err;
1218 }
1219
1220
1221 /* This implements `svn_opt_subcommand_t'. */
1222 static svn_error_t *
1223 subcommand_lstxns(apr_getopt_t *os, void *baton, apr_pool_t *pool)
1224 {
1225   struct svnadmin_opt_state *opt_state = baton;
1226   svn_repos_t *repos;
1227   svn_fs_t *fs;
1228   apr_array_header_t *txns;
1229   int i;
1230
1231   /* Expect no more arguments. */
1232   SVN_ERR(parse_args(NULL, os, 0, 0, pool));
1233
1234   SVN_ERR(open_repos(&repos, opt_state->repository_path, pool));
1235   fs = svn_repos_fs(repos);
1236   SVN_ERR(svn_fs_list_transactions(&txns, fs, pool));
1237
1238   /* Loop, printing revisions. */
1239   for (i = 0; i < txns->nelts; i++)
1240     {
1241       SVN_ERR(svn_cmdline_printf(pool, "%s\n",
1242                                  APR_ARRAY_IDX(txns, i, const char *)));
1243     }
1244
1245   return SVN_NO_ERROR;
1246 }
1247
1248
1249 /* This implements `svn_opt_subcommand_t'. */
1250 static svn_error_t *
1251 subcommand_recover(apr_getopt_t *os, void *baton, apr_pool_t *pool)
1252 {
1253   svn_revnum_t youngest_rev;
1254   svn_repos_t *repos;
1255   svn_error_t *err;
1256   struct svnadmin_opt_state *opt_state = baton;
1257   svn_stream_t *stdout_stream;
1258
1259   /* Expect no more arguments. */
1260   SVN_ERR(parse_args(NULL, os, 0, 0, pool));
1261
1262   SVN_ERR(svn_stream_for_stdout(&stdout_stream, pool));
1263
1264   /* Restore default signal handlers until after we have acquired the
1265    * exclusive lock so that the user interrupt before we actually
1266    * touch the repository. */
1267   setup_cancellation_signals(SIG_DFL);
1268
1269   err = svn_repos_recover4(opt_state->repository_path, TRUE,
1270                            repos_notify_handler, stdout_stream,
1271                            check_cancel, NULL, pool);
1272   if (err)
1273     {
1274       if (! APR_STATUS_IS_EAGAIN(err->apr_err))
1275         return err;
1276       svn_error_clear(err);
1277       if (! opt_state->wait)
1278         return svn_error_create(SVN_ERR_REPOS_LOCKED, NULL,
1279                                 _("Failed to get exclusive repository "
1280                                   "access; perhaps another process\n"
1281                                   "such as httpd, svnserve or svn "
1282                                   "has it open?"));
1283       SVN_ERR(svn_cmdline_printf(pool,
1284                                  _("Waiting on repository lock; perhaps"
1285                                    " another process has it open?\n")));
1286       SVN_ERR(svn_cmdline_fflush(stdout));
1287       SVN_ERR(svn_repos_recover4(opt_state->repository_path, FALSE,
1288                                  repos_notify_handler, stdout_stream,
1289                                  check_cancel, NULL, pool));
1290     }
1291
1292   SVN_ERR(svn_cmdline_printf(pool, _("\nRecovery completed.\n")));
1293
1294   /* Since db transactions may have been replayed, it's nice to tell
1295      people what the latest revision is.  It also proves that the
1296      recovery actually worked. */
1297   SVN_ERR(open_repos(&repos, opt_state->repository_path, pool));
1298   SVN_ERR(svn_fs_youngest_rev(&youngest_rev, svn_repos_fs(repos), pool));
1299   SVN_ERR(svn_cmdline_printf(pool, _("The latest repos revision is %ld.\n"),
1300                              youngest_rev));
1301
1302   return SVN_NO_ERROR;
1303 }
1304
1305
1306 /* This implements `svn_opt_subcommand_t'. */
1307 static svn_error_t *
1308 list_dblogs(apr_getopt_t *os, void *baton, svn_boolean_t only_unused,
1309             apr_pool_t *pool)
1310 {
1311   struct svnadmin_opt_state *opt_state = baton;
1312   apr_array_header_t *logfiles;
1313   int i;
1314
1315   /* Expect no more arguments. */
1316   SVN_ERR(parse_args(NULL, os, 0, 0, pool));
1317
1318   SVN_ERR(svn_repos_db_logfiles(&logfiles,
1319                                 opt_state->repository_path,
1320                                 only_unused,
1321                                 pool));
1322
1323   /* Loop, printing log files.  We append the log paths to the
1324      repository path, making sure to return everything to the native
1325      style before printing. */
1326   for (i = 0; i < logfiles->nelts; i++)
1327     {
1328       const char *log_utf8;
1329       log_utf8 = svn_dirent_join(opt_state->repository_path,
1330                                  APR_ARRAY_IDX(logfiles, i, const char *),
1331                                  pool);
1332       log_utf8 = svn_dirent_local_style(log_utf8, pool);
1333       SVN_ERR(svn_cmdline_printf(pool, "%s\n", log_utf8));
1334     }
1335
1336   return SVN_NO_ERROR;
1337 }
1338
1339
1340 /* This implements `svn_opt_subcommand_t'. */
1341 static svn_error_t *
1342 subcommand_list_dblogs(apr_getopt_t *os, void *baton, apr_pool_t *pool)
1343 {
1344   SVN_ERR(list_dblogs(os, baton, FALSE, pool));
1345   return SVN_NO_ERROR;
1346 }
1347
1348
1349 /* This implements `svn_opt_subcommand_t'. */
1350 static svn_error_t *
1351 subcommand_list_unused_dblogs(apr_getopt_t *os, void *baton, apr_pool_t *pool)
1352 {
1353   /* Expect no more arguments. */
1354   SVN_ERR(parse_args(NULL, os, 0, 0, pool));
1355
1356   SVN_ERR(list_dblogs(os, baton, TRUE, pool));
1357   return SVN_NO_ERROR;
1358 }
1359
1360
1361 /* This implements `svn_opt_subcommand_t'. */
1362 static svn_error_t *
1363 subcommand_rmtxns(apr_getopt_t *os, void *baton, apr_pool_t *pool)
1364 {
1365   struct svnadmin_opt_state *opt_state = baton;
1366   svn_repos_t *repos;
1367   svn_fs_t *fs;
1368   svn_fs_txn_t *txn;
1369   apr_array_header_t *args;
1370   int i;
1371   apr_pool_t *subpool = svn_pool_create(pool);
1372
1373   SVN_ERR(svn_opt_parse_all_args(&args, os, pool));
1374
1375   SVN_ERR(open_repos(&repos, opt_state->repository_path, pool));
1376   fs = svn_repos_fs(repos);
1377
1378   /* All the rest of the arguments are transaction names. */
1379   for (i = 0; i < args->nelts; i++)
1380     {
1381       const char *txn_name = APR_ARRAY_IDX(args, i, const char *);
1382       const char *txn_name_utf8;
1383       svn_error_t *err;
1384
1385       svn_pool_clear(subpool);
1386
1387       SVN_ERR(svn_utf_cstring_to_utf8(&txn_name_utf8, txn_name, subpool));
1388
1389       /* Try to open the txn.  If that succeeds, try to abort it. */
1390       err = svn_fs_open_txn(&txn, fs, txn_name_utf8, subpool);
1391       if (! err)
1392         err = svn_fs_abort_txn(txn, subpool);
1393
1394       /* If either the open or the abort of the txn fails because that
1395          transaction is dead, just try to purge the thing.  Else,
1396          there was either an error worth reporting, or not error at
1397          all.  */
1398       if (err && (err->apr_err == SVN_ERR_FS_TRANSACTION_DEAD))
1399         {
1400           svn_error_clear(err);
1401           err = svn_fs_purge_txn(fs, txn_name_utf8, subpool);
1402         }
1403
1404       /* If we had a real from the txn open, abort, or purge, we clear
1405          that error and just report to the user that we had an issue
1406          with this particular txn. */
1407       if (err)
1408         {
1409           svn_handle_error2(err, stderr, FALSE /* non-fatal */, "svnadmin: ");
1410           svn_error_clear(err);
1411         }
1412       else if (! opt_state->quiet)
1413         {
1414           SVN_ERR(svn_cmdline_printf(subpool, _("Transaction '%s' removed.\n"),
1415                                      txn_name));
1416         }
1417     }
1418
1419   svn_pool_destroy(subpool);
1420
1421   return SVN_NO_ERROR;
1422 }
1423
1424
1425 /* A helper for the 'setrevprop' and 'setlog' commands.  Expects
1426    OPT_STATE->use_pre_revprop_change_hook and
1427    OPT_STATE->use_post_revprop_change_hook to be set appropriately. */
1428 static svn_error_t *
1429 set_revprop(const char *prop_name, const char *filename,
1430             struct svnadmin_opt_state *opt_state, apr_pool_t *pool)
1431 {
1432   svn_repos_t *repos;
1433   svn_string_t *prop_value = svn_string_create_empty(pool);
1434   svn_stringbuf_t *file_contents;
1435
1436   SVN_ERR(svn_stringbuf_from_file2(&file_contents, filename, pool));
1437
1438   prop_value->data = file_contents->data;
1439   prop_value->len = file_contents->len;
1440
1441   SVN_ERR(svn_subst_translate_string2(&prop_value, NULL, NULL, prop_value,
1442                                       NULL, FALSE, pool, pool));
1443
1444   /* Open the filesystem  */
1445   SVN_ERR(open_repos(&repos, opt_state->repository_path, pool));
1446
1447   /* If we are bypassing the hooks system, we just hit the filesystem
1448      directly. */
1449   SVN_ERR(svn_repos_fs_change_rev_prop4(
1450               repos, opt_state->start_revision.value.number,
1451               NULL, prop_name, NULL, prop_value,
1452               opt_state->use_pre_revprop_change_hook,
1453               opt_state->use_post_revprop_change_hook,
1454               NULL, NULL, pool));
1455
1456   return SVN_NO_ERROR;
1457 }
1458
1459
1460 /* This implements `svn_opt_subcommand_t'. */
1461 static svn_error_t *
1462 subcommand_setrevprop(apr_getopt_t *os, void *baton, apr_pool_t *pool)
1463 {
1464   struct svnadmin_opt_state *opt_state = baton;
1465   apr_array_header_t *args;
1466   const char *prop_name, *filename;
1467
1468   /* Expect two more arguments: NAME FILE */
1469   SVN_ERR(parse_args(&args, os, 2, 2, pool));
1470   prop_name = APR_ARRAY_IDX(args, 0, const char *);
1471   filename = APR_ARRAY_IDX(args, 1, const char *);
1472   SVN_ERR(target_arg_to_dirent(&filename, filename, pool));
1473
1474   if (opt_state->start_revision.kind != svn_opt_revision_number)
1475     return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
1476                              _("Missing revision"));
1477   else if (opt_state->end_revision.kind != svn_opt_revision_unspecified)
1478     return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
1479                              _("Only one revision allowed"));
1480
1481   return set_revprop(prop_name, filename, opt_state, pool);
1482 }
1483
1484
1485 /* This implements `svn_opt_subcommand_t'. */
1486 static svn_error_t *
1487 subcommand_setuuid(apr_getopt_t *os, void *baton, apr_pool_t *pool)
1488 {
1489   struct svnadmin_opt_state *opt_state = baton;
1490   apr_array_header_t *args;
1491   svn_repos_t *repos;
1492   svn_fs_t *fs;
1493   const char *uuid = NULL;
1494
1495   /* Expect zero or one more arguments: [UUID] */
1496   SVN_ERR(parse_args(&args, os, 0, 1, pool));
1497   if (args->nelts == 1)
1498     uuid = APR_ARRAY_IDX(args, 0, const char *);
1499
1500   SVN_ERR(open_repos(&repos, opt_state->repository_path, pool));
1501   fs = svn_repos_fs(repos);
1502   return svn_fs_set_uuid(fs, uuid, pool);
1503 }
1504
1505
1506 /* This implements `svn_opt_subcommand_t'. */
1507 static svn_error_t *
1508 subcommand_setlog(apr_getopt_t *os, void *baton, apr_pool_t *pool)
1509 {
1510   struct svnadmin_opt_state *opt_state = baton;
1511   apr_array_header_t *args;
1512   const char *filename;
1513
1514   /* Expect one more argument: FILE */
1515   SVN_ERR(parse_args(&args, os, 1, 1, pool));
1516   filename = APR_ARRAY_IDX(args, 0, const char *);
1517   SVN_ERR(target_arg_to_dirent(&filename, filename, pool));
1518
1519   if (opt_state->start_revision.kind != svn_opt_revision_number)
1520     return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
1521                              _("Missing revision"));
1522   else if (opt_state->end_revision.kind != svn_opt_revision_unspecified)
1523     return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
1524                              _("Only one revision allowed"));
1525
1526   /* set_revprop() responds only to pre-/post-revprop-change opts. */
1527   if (!opt_state->bypass_hooks)
1528     {
1529       opt_state->use_pre_revprop_change_hook = TRUE;
1530       opt_state->use_post_revprop_change_hook = TRUE;
1531     }
1532
1533   return set_revprop(SVN_PROP_REVISION_LOG, filename, opt_state, pool);
1534 }
1535
1536
1537 /* This implements 'svn_opt_subcommand_t'. */
1538 static svn_error_t *
1539 subcommand_pack(apr_getopt_t *os, void *baton, apr_pool_t *pool)
1540 {
1541   struct svnadmin_opt_state *opt_state = baton;
1542   svn_repos_t *repos;
1543   svn_stream_t *progress_stream = NULL;
1544
1545   /* Expect no more arguments. */
1546   SVN_ERR(parse_args(NULL, os, 0, 0, pool));
1547
1548   SVN_ERR(open_repos(&repos, opt_state->repository_path, pool));
1549
1550   /* Progress feedback goes to STDOUT, unless they asked to suppress it. */
1551   if (! opt_state->quiet)
1552     progress_stream = recode_stream_create(stdout, pool);
1553
1554   return svn_error_trace(
1555     svn_repos_fs_pack2(repos, !opt_state->quiet ? repos_notify_handler : NULL,
1556                        progress_stream, check_cancel, NULL, pool));
1557 }
1558
1559
1560 /* This implements `svn_opt_subcommand_t'. */
1561 static svn_error_t *
1562 subcommand_verify(apr_getopt_t *os, void *baton, apr_pool_t *pool)
1563 {
1564   struct svnadmin_opt_state *opt_state = baton;
1565   svn_repos_t *repos;
1566   svn_fs_t *fs;
1567   svn_revnum_t youngest, lower, upper;
1568   svn_stream_t *progress_stream = NULL;
1569
1570   /* Expect no more arguments. */
1571   SVN_ERR(parse_args(NULL, os, 0, 0, pool));
1572
1573   if (opt_state->txn_id
1574       && (opt_state->start_revision.kind != svn_opt_revision_unspecified
1575           || opt_state->end_revision.kind != svn_opt_revision_unspecified))
1576     {
1577       return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
1578                                _("--revision (-r) and --transaction (-t) "
1579                                  "are mutually exclusive"));
1580     }
1581
1582   SVN_ERR(open_repos(&repos, opt_state->repository_path, pool));
1583   fs = svn_repos_fs(repos);
1584   SVN_ERR(svn_fs_youngest_rev(&youngest, fs, pool));
1585
1586   /* Usage 2. */
1587   if (opt_state->txn_id)
1588     {
1589       svn_fs_txn_t *txn;
1590       svn_fs_root_t *root;
1591
1592       SVN_ERR(svn_fs_open_txn(&txn, fs, opt_state->txn_id, pool));
1593       SVN_ERR(svn_fs_txn_root(&root, txn, pool));
1594       SVN_ERR(svn_fs_verify_root(root, pool));
1595       return SVN_NO_ERROR;
1596     }
1597   else
1598     /* Usage 1. */
1599     ;
1600
1601   /* Find the revision numbers at which to start and end. */
1602   SVN_ERR(get_revnum(&lower, &opt_state->start_revision,
1603                      youngest, repos, pool));
1604   SVN_ERR(get_revnum(&upper, &opt_state->end_revision,
1605                      youngest, repos, pool));
1606
1607   if (upper == SVN_INVALID_REVNUM)
1608     {
1609       upper = lower;
1610     }
1611
1612   if (! opt_state->quiet)
1613     progress_stream = recode_stream_create(stderr, pool);
1614
1615   return svn_repos_verify_fs2(repos, lower, upper,
1616                               !opt_state->quiet
1617                                 ? repos_notify_handler : NULL,
1618                               progress_stream, check_cancel, NULL, pool);
1619 }
1620
1621 /* This implements `svn_opt_subcommand_t'. */
1622 svn_error_t *
1623 subcommand_hotcopy(apr_getopt_t *os, void *baton, apr_pool_t *pool)
1624 {
1625   struct svnadmin_opt_state *opt_state = baton;
1626   apr_array_header_t *targets;
1627   const char *new_repos_path;
1628
1629   /* Expect one more argument: NEW_REPOS_PATH */
1630   SVN_ERR(parse_args(&targets, os, 1, 1, pool));
1631   new_repos_path = APR_ARRAY_IDX(targets, 0, const char *);
1632   SVN_ERR(target_arg_to_dirent(&new_repos_path, new_repos_path, pool));
1633
1634   return svn_repos_hotcopy2(opt_state->repository_path, new_repos_path,
1635                             opt_state->clean_logs, opt_state->incremental,
1636                             check_cancel, NULL, pool);
1637 }
1638
1639 /* This implements `svn_opt_subcommand_t'. */
1640 static svn_error_t *
1641 subcommand_lock(apr_getopt_t *os, void *baton, apr_pool_t *pool)
1642 {
1643   struct svnadmin_opt_state *opt_state = baton;
1644   svn_repos_t *repos;
1645   svn_fs_t *fs;
1646   svn_fs_access_t *access;
1647   apr_array_header_t *args;
1648   const char *username;
1649   const char *lock_path;
1650   const char *comment_file_name;
1651   svn_stringbuf_t *file_contents;
1652   const char *lock_path_utf8;
1653   svn_lock_t *lock;
1654   const char *lock_token = NULL;
1655
1656   /* Expect three more arguments: PATH USERNAME COMMENT-FILE */
1657   SVN_ERR(parse_args(&args, os, 3, 4, pool));
1658   lock_path = APR_ARRAY_IDX(args, 0, const char *);
1659   username = APR_ARRAY_IDX(args, 1, const char *);
1660   comment_file_name = APR_ARRAY_IDX(args, 2, const char *);
1661
1662   /* Expect one more optional argument: TOKEN */
1663   if (args->nelts == 4)
1664     lock_token = APR_ARRAY_IDX(args, 3, const char *);
1665
1666   SVN_ERR(target_arg_to_dirent(&comment_file_name, comment_file_name, pool));
1667
1668   SVN_ERR(open_repos(&repos, opt_state->repository_path, pool));
1669   fs = svn_repos_fs(repos);
1670
1671   /* Create an access context describing the user. */
1672   SVN_ERR(svn_fs_create_access(&access, username, pool));
1673
1674   /* Attach the access context to the filesystem. */
1675   SVN_ERR(svn_fs_set_access(fs, access));
1676
1677   SVN_ERR(svn_stringbuf_from_file2(&file_contents, comment_file_name, pool));
1678
1679   SVN_ERR(svn_utf_cstring_to_utf8(&lock_path_utf8, lock_path, pool));
1680
1681   if (opt_state->bypass_hooks)
1682     SVN_ERR(svn_fs_lock(&lock, fs, lock_path_utf8,
1683                         lock_token,
1684                         file_contents->data, /* comment */
1685                         0,                   /* is_dav_comment */
1686                         0,                   /* no expiration time. */
1687                         SVN_INVALID_REVNUM,
1688                         FALSE, pool));
1689   else
1690     SVN_ERR(svn_repos_fs_lock(&lock, repos, lock_path_utf8,
1691                               lock_token,
1692                               file_contents->data, /* comment */
1693                               0,                   /* is_dav_comment */
1694                               0,                   /* no expiration time. */
1695                               SVN_INVALID_REVNUM,
1696                               FALSE, pool));
1697
1698   SVN_ERR(svn_cmdline_printf(pool, _("'%s' locked by user '%s'.\n"),
1699                              lock_path, username));
1700   return SVN_NO_ERROR;
1701 }
1702
1703 static svn_error_t *
1704 subcommand_lslocks(apr_getopt_t *os, void *baton, apr_pool_t *pool)
1705 {
1706   struct svnadmin_opt_state *opt_state = baton;
1707   apr_array_header_t *targets;
1708   svn_repos_t *repos;
1709   const char *fs_path = "/";
1710   apr_hash_t *locks;
1711   apr_hash_index_t *hi;
1712
1713   SVN_ERR(svn_opt__args_to_target_array(&targets, os,
1714                                         apr_array_make(pool, 0,
1715                                                        sizeof(const char *)),
1716                                         pool));
1717   if (targets->nelts > 1)
1718     return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, 0,
1719                             _("Too many arguments given"));
1720   if (targets->nelts)
1721     fs_path = APR_ARRAY_IDX(targets, 0, const char *);
1722
1723   SVN_ERR(open_repos(&repos, opt_state->repository_path, pool));
1724
1725   /* Fetch all locks on or below the root directory. */
1726   SVN_ERR(svn_repos_fs_get_locks2(&locks, repos, fs_path, svn_depth_infinity,
1727                                   NULL, NULL, pool));
1728
1729   for (hi = apr_hash_first(pool, locks); hi; hi = apr_hash_next(hi))
1730     {
1731       const char *cr_date, *exp_date = "";
1732       const char *path = svn__apr_hash_index_key(hi);
1733       svn_lock_t *lock = svn__apr_hash_index_val(hi);
1734       int comment_lines = 0;
1735
1736       cr_date = svn_time_to_human_cstring(lock->creation_date, pool);
1737
1738       if (lock->expiration_date)
1739         exp_date = svn_time_to_human_cstring(lock->expiration_date, pool);
1740
1741       if (lock->comment)
1742         comment_lines = svn_cstring_count_newlines(lock->comment) + 1;
1743
1744       SVN_ERR(svn_cmdline_printf(pool, _("Path: %s\n"), path));
1745       SVN_ERR(svn_cmdline_printf(pool, _("UUID Token: %s\n"), lock->token));
1746       SVN_ERR(svn_cmdline_printf(pool, _("Owner: %s\n"), lock->owner));
1747       SVN_ERR(svn_cmdline_printf(pool, _("Created: %s\n"), cr_date));
1748       SVN_ERR(svn_cmdline_printf(pool, _("Expires: %s\n"), exp_date));
1749       SVN_ERR(svn_cmdline_printf(pool,
1750                                  Q_("Comment (%i line):\n%s\n\n",
1751                                     "Comment (%i lines):\n%s\n\n",
1752                                     comment_lines),
1753                                  comment_lines,
1754                                  lock->comment ? lock->comment : ""));
1755     }
1756
1757   return SVN_NO_ERROR;
1758 }
1759
1760
1761
1762 static svn_error_t *
1763 subcommand_rmlocks(apr_getopt_t *os, void *baton, apr_pool_t *pool)
1764 {
1765   struct svnadmin_opt_state *opt_state = baton;
1766   svn_repos_t *repos;
1767   svn_fs_t *fs;
1768   svn_fs_access_t *access;
1769   svn_error_t *err;
1770   apr_array_header_t *args;
1771   int i;
1772   const char *username;
1773   apr_pool_t *subpool = svn_pool_create(pool);
1774
1775   SVN_ERR(open_repos(&repos, opt_state->repository_path, pool));
1776   fs = svn_repos_fs(repos);
1777
1778   /* svn_fs_unlock() demands that some username be associated with the
1779      filesystem, so just use the UID of the person running 'svnadmin'.*/
1780   username = svn_user_get_name(pool);
1781   if (! username)
1782     username = "administrator";
1783
1784   /* Create an access context describing the current user. */
1785   SVN_ERR(svn_fs_create_access(&access, username, pool));
1786
1787   /* Attach the access context to the filesystem. */
1788   SVN_ERR(svn_fs_set_access(fs, access));
1789
1790   /* Parse out any options. */
1791   SVN_ERR(svn_opt_parse_all_args(&args, os, pool));
1792
1793   /* Our usage requires at least one FS path. */
1794   if (args->nelts == 0)
1795     return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, 0,
1796                             _("No paths to unlock provided"));
1797
1798   /* All the rest of the arguments are paths from which to remove locks. */
1799   for (i = 0; i < args->nelts; i++)
1800     {
1801       const char *lock_path = APR_ARRAY_IDX(args, i, const char *);
1802       const char *lock_path_utf8;
1803       svn_lock_t *lock;
1804
1805       SVN_ERR(svn_utf_cstring_to_utf8(&lock_path_utf8, lock_path, subpool));
1806
1807       /* Fetch the path's svn_lock_t. */
1808       err = svn_fs_get_lock(&lock, fs, lock_path_utf8, subpool);
1809       if (err)
1810         goto move_on;
1811       if (! lock)
1812         {
1813           SVN_ERR(svn_cmdline_printf(subpool,
1814                                      _("Path '%s' isn't locked.\n"),
1815                                      lock_path));
1816           continue;
1817         }
1818
1819       /* Now forcibly destroy the lock. */
1820       err = svn_fs_unlock(fs, lock_path_utf8,
1821                           lock->token, 1 /* force */, subpool);
1822       if (err)
1823         goto move_on;
1824
1825       SVN_ERR(svn_cmdline_printf(subpool,
1826                                  _("Removed lock on '%s'.\n"), lock->path));
1827
1828     move_on:
1829       if (err)
1830         {
1831           /* Print the error, but move on to the next lock. */
1832           svn_handle_error2(err, stderr, FALSE /* non-fatal */, "svnadmin: ");
1833           svn_error_clear(err);
1834         }
1835
1836       svn_pool_clear(subpool);
1837     }
1838
1839   svn_pool_destroy(subpool);
1840   return SVN_NO_ERROR;
1841 }
1842
1843
1844 /* This implements `svn_opt_subcommand_t'. */
1845 static svn_error_t *
1846 subcommand_unlock(apr_getopt_t *os, void *baton, apr_pool_t *pool)
1847 {
1848   struct svnadmin_opt_state *opt_state = baton;
1849   svn_repos_t *repos;
1850   svn_fs_t *fs;
1851   svn_fs_access_t *access;
1852   apr_array_header_t *args;
1853   const char *username;
1854   const char *lock_path;
1855   const char *lock_path_utf8;
1856   const char *lock_token = NULL;
1857
1858   /* Expect three more arguments: PATH USERNAME TOKEN */
1859   SVN_ERR(parse_args(&args, os, 3, 3, pool));
1860   lock_path = APR_ARRAY_IDX(args, 0, const char *);
1861   username = APR_ARRAY_IDX(args, 1, const char *);
1862   lock_token = APR_ARRAY_IDX(args, 2, const char *);
1863
1864   /* Open the repos/FS, and associate an access context containing
1865      USERNAME. */
1866   SVN_ERR(open_repos(&repos, opt_state->repository_path, pool));
1867   fs = svn_repos_fs(repos);
1868   SVN_ERR(svn_fs_create_access(&access, username, pool));
1869   SVN_ERR(svn_fs_set_access(fs, access));
1870
1871   SVN_ERR(svn_utf_cstring_to_utf8(&lock_path_utf8, lock_path, pool));
1872   if (opt_state->bypass_hooks)
1873     SVN_ERR(svn_fs_unlock(fs, lock_path_utf8, lock_token,
1874                           FALSE, pool));
1875   else
1876     SVN_ERR(svn_repos_fs_unlock(repos, lock_path_utf8, lock_token,
1877                                 FALSE, pool));
1878
1879   SVN_ERR(svn_cmdline_printf(pool, _("'%s' unlocked by user '%s'.\n"),
1880                              lock_path, username));
1881   return SVN_NO_ERROR;
1882 }
1883
1884
1885 /* This implements `svn_opt_subcommand_t'. */
1886 static svn_error_t *
1887 subcommand_upgrade(apr_getopt_t *os, void *baton, apr_pool_t *pool)
1888 {
1889   svn_error_t *err;
1890   struct svnadmin_opt_state *opt_state = baton;
1891   svn_stream_t *stdout_stream;
1892
1893   /* Expect no more arguments. */
1894   SVN_ERR(parse_args(NULL, os, 0, 0, pool));
1895
1896   SVN_ERR(svn_stream_for_stdout(&stdout_stream, pool));
1897
1898   /* Restore default signal handlers. */
1899   setup_cancellation_signals(SIG_DFL);
1900
1901   err = svn_repos_upgrade2(opt_state->repository_path, TRUE,
1902                            repos_notify_handler, stdout_stream, pool);
1903   if (err)
1904     {
1905       if (APR_STATUS_IS_EAGAIN(err->apr_err))
1906         {
1907           svn_error_clear(err);
1908           err = SVN_NO_ERROR;
1909           if (! opt_state->wait)
1910             return svn_error_create(SVN_ERR_REPOS_LOCKED, NULL,
1911                                     _("Failed to get exclusive repository "
1912                                       "access; perhaps another process\n"
1913                                       "such as httpd, svnserve or svn "
1914                                       "has it open?"));
1915           SVN_ERR(svn_cmdline_printf(pool,
1916                                      _("Waiting on repository lock; perhaps"
1917                                        " another process has it open?\n")));
1918           SVN_ERR(svn_cmdline_fflush(stdout));
1919           SVN_ERR(svn_repos_upgrade2(opt_state->repository_path, FALSE,
1920                                      repos_notify_handler, stdout_stream,
1921                                      pool));
1922         }
1923       else if (err->apr_err == SVN_ERR_FS_UNSUPPORTED_UPGRADE)
1924         {
1925           return svn_error_quick_wrap(err,
1926                     _("Upgrade of this repository's underlying versioned "
1927                     "filesystem is not supported; consider "
1928                     "dumping and loading the data elsewhere"));
1929         }
1930       else if (err->apr_err == SVN_ERR_REPOS_UNSUPPORTED_UPGRADE)
1931         {
1932           return svn_error_quick_wrap(err,
1933                     _("Upgrade of this repository is not supported; consider "
1934                     "dumping and loading the data elsewhere"));
1935         }
1936     }
1937   SVN_ERR(err);
1938
1939   SVN_ERR(svn_cmdline_printf(pool, _("\nUpgrade completed.\n")));
1940   return SVN_NO_ERROR;
1941 }
1942
1943
1944 \f
1945 /** Main. **/
1946
1947 /* Report and clear the error ERR, and return EXIT_FAILURE. */
1948 #define EXIT_ERROR(err)                                                 \
1949   svn_cmdline_handle_exit_error(err, NULL, "svnadmin: ")
1950
1951 /* A redefinition of the public SVN_INT_ERR macro, that suppresses the
1952  * error message if it is SVN_ERR_IO_PIPE_WRITE_ERROR, amd with the
1953  * program name 'svnadmin' instead of 'svn'. */
1954 #undef SVN_INT_ERR
1955 #define SVN_INT_ERR(expr)                                        \
1956   do {                                                           \
1957     svn_error_t *svn_err__temp = (expr);                         \
1958     if (svn_err__temp)                                           \
1959       return EXIT_ERROR(svn_err__temp);                          \
1960   } while (0)
1961
1962 static int
1963 sub_main(int argc, const char *argv[], apr_pool_t *pool)
1964 {
1965   svn_error_t *err;
1966   apr_status_t apr_err;
1967
1968   const svn_opt_subcommand_desc2_t *subcommand = NULL;
1969   struct svnadmin_opt_state opt_state = { 0 };
1970   apr_getopt_t *os;
1971   int opt_id;
1972   apr_array_header_t *received_opts;
1973   int i;
1974   svn_boolean_t dash_F_arg = FALSE;
1975
1976   received_opts = apr_array_make(pool, SVN_OPT_MAX_OPTIONS, sizeof(int));
1977
1978   /* Check library versions */
1979   SVN_INT_ERR(check_lib_versions());
1980
1981   /* Initialize the FS library. */
1982   SVN_INT_ERR(svn_fs_initialize(pool));
1983
1984   if (argc <= 1)
1985     {
1986       SVN_INT_ERR(subcommand_help(NULL, NULL, pool));
1987       return EXIT_FAILURE;
1988     }
1989
1990   /* Initialize opt_state. */
1991   opt_state.start_revision.kind = svn_opt_revision_unspecified;
1992   opt_state.end_revision.kind = svn_opt_revision_unspecified;
1993   opt_state.memory_cache_size = svn_cache_config_get()->cache_size;
1994
1995   /* Parse options. */
1996   SVN_INT_ERR(svn_cmdline__getopt_init(&os, argc, argv, pool));
1997
1998   os->interleave = 1;
1999
2000   while (1)
2001     {
2002       const char *opt_arg;
2003       const char *utf8_opt_arg;
2004
2005       /* Parse the next option. */
2006       apr_err = apr_getopt_long(os, options_table, &opt_id, &opt_arg);
2007       if (APR_STATUS_IS_EOF(apr_err))
2008         break;
2009       else if (apr_err)
2010         {
2011           SVN_INT_ERR(subcommand_help(NULL, NULL, pool));
2012           return EXIT_FAILURE;
2013         }
2014
2015       /* Stash the option code in an array before parsing it. */
2016       APR_ARRAY_PUSH(received_opts, int) = opt_id;
2017
2018       switch (opt_id) {
2019       case 'r':
2020         {
2021           if (opt_state.start_revision.kind != svn_opt_revision_unspecified)
2022             {
2023               err = svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
2024                  _("Multiple revision arguments encountered; "
2025                    "try '-r N:M' instead of '-r N -r M'"));
2026               return EXIT_ERROR(err);
2027             }
2028           if (svn_opt_parse_revision(&(opt_state.start_revision),
2029                                      &(opt_state.end_revision),
2030                                      opt_arg, pool) != 0)
2031             {
2032               err = svn_utf_cstring_to_utf8(&utf8_opt_arg, opt_arg,
2033                                             pool);
2034
2035               if (! err)
2036                 err = svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
2037                         _("Syntax error in revision argument '%s'"),
2038                         utf8_opt_arg);
2039               return EXIT_ERROR(err);
2040             }
2041         }
2042         break;
2043       case 't':
2044         opt_state.txn_id = opt_arg;
2045         break;
2046
2047       case 'q':
2048         opt_state.quiet = TRUE;
2049         break;
2050       case 'h':
2051       case '?':
2052         opt_state.help = TRUE;
2053         break;
2054       case 'M':
2055         opt_state.memory_cache_size
2056             = 0x100000 * apr_strtoi64(opt_arg, NULL, 0);
2057         break;
2058       case 'F':
2059         SVN_INT_ERR(svn_utf_cstring_to_utf8(&utf8_opt_arg, opt_arg, pool));
2060         SVN_INT_ERR(svn_stringbuf_from_file2(&(opt_state.filedata),
2061                                              utf8_opt_arg, pool));
2062         dash_F_arg = TRUE;
2063       case svnadmin__version:
2064         opt_state.version = TRUE;
2065         break;
2066       case svnadmin__incremental:
2067         opt_state.incremental = TRUE;
2068         break;
2069       case svnadmin__deltas:
2070         opt_state.use_deltas = TRUE;
2071         break;
2072       case svnadmin__ignore_uuid:
2073         opt_state.uuid_action = svn_repos_load_uuid_ignore;
2074         break;
2075       case svnadmin__force_uuid:
2076         opt_state.uuid_action = svn_repos_load_uuid_force;
2077         break;
2078       case svnadmin__pre_1_4_compatible:
2079         opt_state.pre_1_4_compatible = TRUE;
2080         break;
2081       case svnadmin__pre_1_5_compatible:
2082         opt_state.pre_1_5_compatible = TRUE;
2083         break;
2084       case svnadmin__pre_1_6_compatible:
2085         opt_state.pre_1_6_compatible = TRUE;
2086         break;
2087       case svnadmin__compatible_version:
2088         {
2089           svn_version_t latest = { SVN_VER_MAJOR, SVN_VER_MINOR,
2090                                    SVN_VER_PATCH, NULL };
2091           svn_version_t *compatible_version;
2092
2093           /* Parse the version string which carries our target
2094              compatibility. */
2095           SVN_INT_ERR(svn_version__parse_version_string(&compatible_version,
2096                                                         opt_arg, pool));
2097
2098           /* We can't create repository with a version older than 1.0.0.  */
2099           if (! svn_version__at_least(compatible_version, 1, 0, 0))
2100             {
2101               err = svn_error_createf(SVN_ERR_UNSUPPORTED_FEATURE, NULL,
2102                                       _("Cannot create pre-1.0-compatible "
2103                                         "repositories"));
2104               return EXIT_ERROR(err);
2105             }
2106
2107           /* We can't create repository with a version newer than what
2108              the running version of Subversion supports. */
2109           if (! svn_version__at_least(&latest,
2110                                       compatible_version->major,
2111                                       compatible_version->minor,
2112                                       compatible_version->patch))
2113             {
2114               err = svn_error_createf(SVN_ERR_UNSUPPORTED_FEATURE, NULL,
2115                                       _("Cannot guarantee compatibility "
2116                                         "beyond the current running version "
2117                                         "(%s)"),
2118                                       SVN_VER_NUM );
2119               return EXIT_ERROR(err);
2120             }
2121
2122           opt_state.compatible_version = compatible_version;
2123         }
2124         break;
2125       case svnadmin__fs_type:
2126         SVN_INT_ERR(svn_utf_cstring_to_utf8(&opt_state.fs_type, opt_arg, pool));
2127         break;
2128       case svnadmin__parent_dir:
2129         SVN_INT_ERR(svn_utf_cstring_to_utf8(&opt_state.parent_dir, opt_arg,
2130                                             pool));
2131         opt_state.parent_dir
2132           = svn_dirent_internal_style(opt_state.parent_dir, pool);
2133         break;
2134       case svnadmin__use_pre_commit_hook:
2135         opt_state.use_pre_commit_hook = TRUE;
2136         break;
2137       case svnadmin__use_post_commit_hook:
2138         opt_state.use_post_commit_hook = TRUE;
2139         break;
2140       case svnadmin__use_pre_revprop_change_hook:
2141         opt_state.use_pre_revprop_change_hook = TRUE;
2142         break;
2143       case svnadmin__use_post_revprop_change_hook:
2144         opt_state.use_post_revprop_change_hook = TRUE;
2145         break;
2146       case svnadmin__bdb_txn_nosync:
2147         opt_state.bdb_txn_nosync = TRUE;
2148         break;
2149       case svnadmin__bdb_log_keep:
2150         opt_state.bdb_log_keep = TRUE;
2151         break;
2152       case svnadmin__bypass_hooks:
2153         opt_state.bypass_hooks = TRUE;
2154         break;
2155       case svnadmin__bypass_prop_validation:
2156         opt_state.bypass_prop_validation = TRUE;
2157         break;
2158       case svnadmin__clean_logs:
2159         opt_state.clean_logs = TRUE;
2160         break;
2161       case svnadmin__config_dir:
2162         SVN_INT_ERR(svn_utf_cstring_to_utf8(&utf8_opt_arg, opt_arg, pool));
2163         opt_state.config_dir =
2164             apr_pstrdup(pool, svn_dirent_canonicalize(utf8_opt_arg, pool));
2165         break;
2166       case svnadmin__wait:
2167         opt_state.wait = TRUE;
2168         break;
2169       default:
2170         {
2171           SVN_INT_ERR(subcommand_help(NULL, NULL, pool));
2172           return EXIT_FAILURE;
2173         }
2174       }  /* close `switch' */
2175     }  /* close `while' */
2176
2177   /* If the user asked for help, then the rest of the arguments are
2178      the names of subcommands to get help on (if any), or else they're
2179      just typos/mistakes.  Whatever the case, the subcommand to
2180      actually run is subcommand_help(). */
2181   if (opt_state.help)
2182     subcommand = svn_opt_get_canonical_subcommand2(cmd_table, "help");
2183
2184   /* If we're not running the `help' subcommand, then look for a
2185      subcommand in the first argument. */
2186   if (subcommand == NULL)
2187     {
2188       if (os->ind >= os->argc)
2189         {
2190           if (opt_state.version)
2191             {
2192               /* Use the "help" subcommand to handle the "--version" option. */
2193               static const svn_opt_subcommand_desc2_t pseudo_cmd =
2194                 { "--version", subcommand_help, {0}, "",
2195                   {svnadmin__version,  /* must accept its own option */
2196                    'q',  /* --quiet */
2197                   } };
2198
2199               subcommand = &pseudo_cmd;
2200             }
2201           else
2202             {
2203               svn_error_clear(svn_cmdline_fprintf(stderr, pool,
2204                                         _("subcommand argument required\n")));
2205               SVN_INT_ERR(subcommand_help(NULL, NULL, pool));
2206               return EXIT_FAILURE;
2207             }
2208         }
2209       else
2210         {
2211           const char *first_arg = os->argv[os->ind++];
2212           subcommand = svn_opt_get_canonical_subcommand2(cmd_table, first_arg);
2213           if (subcommand == NULL)
2214             {
2215               const char *first_arg_utf8;
2216               SVN_INT_ERR(svn_utf_cstring_to_utf8(&first_arg_utf8,
2217                                                   first_arg, pool));
2218               svn_error_clear(
2219                 svn_cmdline_fprintf(stderr, pool,
2220                                     _("Unknown subcommand: '%s'\n"),
2221                                     first_arg_utf8));
2222               SVN_INT_ERR(subcommand_help(NULL, NULL, pool));
2223               return EXIT_FAILURE;
2224             }
2225         }
2226     }
2227
2228   /* Every subcommand except `help' and `freeze' with '-F' require a
2229      second argument -- the repository path.  Parse it out here and
2230      store it in opt_state. */
2231   if (!(subcommand->cmd_func == subcommand_help
2232         || (subcommand->cmd_func == subcommand_freeze && dash_F_arg)))
2233     {
2234       const char *repos_path = NULL;
2235
2236       if (os->ind >= os->argc)
2237         {
2238           err = svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
2239                                  _("Repository argument required"));
2240           return EXIT_ERROR(err);
2241         }
2242
2243       if ((err = svn_utf_cstring_to_utf8(&repos_path,
2244                                          os->argv[os->ind++], pool)))
2245         {
2246           return EXIT_ERROR(err);
2247         }
2248
2249       if (svn_path_is_url(repos_path))
2250         {
2251           err = svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
2252                                   _("'%s' is a URL when it should be a "
2253                                     "local path"), repos_path);
2254           return EXIT_ERROR(err);
2255         }
2256
2257       opt_state.repository_path = svn_dirent_internal_style(repos_path, pool);
2258     }
2259
2260   /* Check that the subcommand wasn't passed any inappropriate options. */
2261   for (i = 0; i < received_opts->nelts; i++)
2262     {
2263       opt_id = APR_ARRAY_IDX(received_opts, i, int);
2264
2265       /* All commands implicitly accept --help, so just skip over this
2266          when we see it. Note that we don't want to include this option
2267          in their "accepted options" list because it would be awfully
2268          redundant to display it in every commands' help text. */
2269       if (opt_id == 'h' || opt_id == '?')
2270         continue;
2271
2272       if (! svn_opt_subcommand_takes_option3(subcommand, opt_id, NULL))
2273         {
2274           const char *optstr;
2275           const apr_getopt_option_t *badopt =
2276             svn_opt_get_option_from_code2(opt_id, options_table, subcommand,
2277                                           pool);
2278           svn_opt_format_option(&optstr, badopt, FALSE, pool);
2279           if (subcommand->name[0] == '-')
2280             SVN_INT_ERR(subcommand_help(NULL, NULL, pool));
2281           else
2282             svn_error_clear(svn_cmdline_fprintf(stderr, pool
2283                             , _("Subcommand '%s' doesn't accept option '%s'\n"
2284                                 "Type 'svnadmin help %s' for usage.\n"),
2285                 subcommand->name, optstr, subcommand->name));
2286           return EXIT_FAILURE;
2287         }
2288     }
2289
2290   /* Set up our cancellation support. */
2291   setup_cancellation_signals(signal_handler);
2292
2293 #ifdef SIGPIPE
2294   /* Disable SIGPIPE generation for the platforms that have it. */
2295   apr_signal(SIGPIPE, SIG_IGN);
2296 #endif
2297
2298 #ifdef SIGXFSZ
2299   /* Disable SIGXFSZ generation for the platforms that have it, otherwise
2300    * working with large files when compiled against an APR that doesn't have
2301    * large file support will crash the program, which is uncool. */
2302   apr_signal(SIGXFSZ, SIG_IGN);
2303 #endif
2304
2305   /* Configure FSFS caches for maximum efficiency with svnadmin.
2306    * Also, apply the respective command line parameters, if given. */
2307   {
2308     svn_cache_config_t settings = *svn_cache_config_get();
2309
2310     settings.cache_size = opt_state.memory_cache_size;
2311     settings.single_threaded = TRUE;
2312
2313     svn_cache_config_set(&settings);
2314   }
2315
2316   /* Run the subcommand. */
2317   err = (*subcommand->cmd_func)(os, &opt_state, pool);
2318   if (err)
2319     {
2320       /* For argument-related problems, suggest using the 'help'
2321          subcommand. */
2322       if (err->apr_err == SVN_ERR_CL_INSUFFICIENT_ARGS
2323           || err->apr_err == SVN_ERR_CL_ARG_PARSING_ERROR)
2324         {
2325           err = svn_error_quick_wrap(err,
2326                                      _("Try 'svnadmin help' for more info"));
2327         }
2328       return EXIT_ERROR(err);
2329     }
2330   else
2331     {
2332       /* Ensure that everything is written to stdout, so the user will
2333          see any print errors. */
2334       err = svn_cmdline_fflush(stdout);
2335       if (err)
2336         {
2337           return EXIT_ERROR(err);
2338         }
2339       return EXIT_SUCCESS;
2340     }
2341 }
2342
2343 int
2344 main(int argc, const char *argv[])
2345 {
2346   apr_pool_t *pool;
2347   int exit_code;
2348
2349   /* Initialize the app. */
2350   if (svn_cmdline_init("svnadmin", stderr) != EXIT_SUCCESS)
2351     return EXIT_FAILURE;
2352
2353   /* Create our top-level pool.  Use a separate mutexless allocator,
2354    * given this application is single threaded.
2355    */
2356   pool = apr_allocator_owner_get(svn_pool_create_allocator(FALSE));
2357
2358   exit_code = sub_main(argc, argv, pool);
2359
2360   svn_pool_destroy(pool);
2361   return exit_code;
2362 }