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