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