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