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