Mark many merge tests as skip-against-old-server.
[svn.git] / subversion / svnadmin / main.c
blob746860f0dbeb7df830f18ee68bbdcb6109d3f864
1 /*
2 * main.c: Subversion server administration tool.
4 * ====================================================================
5 * Copyright (c) 2000-2007 CollabNet. All rights reserved.
7 * This software is licensed as described in the file COPYING, which
8 * you should have received as part of this distribution. The terms
9 * are also available at http://subversion.tigris.org/license-1.html.
10 * If newer versions of this license are posted there, you may use a
11 * newer version instead, at your option.
13 * This software consists of voluntary contributions made by many
14 * individuals. For exact contribution history, see the revision
15 * history and logs, available at http://subversion.tigris.org/.
16 * ====================================================================
20 #include <apr_file_io.h>
21 #include <apr_signal.h>
23 #include "svn_pools.h"
24 #include "svn_cmdline.h"
25 #include "svn_error.h"
26 #include "svn_opt.h"
27 #include "svn_utf.h"
28 #include "svn_subst.h"
29 #include "svn_path.h"
30 #include "svn_config.h"
31 #include "svn_repos.h"
32 #include "svn_fs.h"
33 #include "svn_version.h"
34 #include "svn_props.h"
35 #include "svn_time.h"
36 #include "svn_user.h"
38 #include "svn_private_config.h"
41 /*** Code. ***/
43 /* A flag to see if we've been cancelled by the client or not. */
44 static volatile sig_atomic_t cancelled = FALSE;
46 /* A signal handler to support cancellation. */
47 static void
48 signal_handler(int signum)
50 apr_signal(signum, SIG_IGN);
51 cancelled = TRUE;
55 /* A helper to set up the cancellation signal handlers. */
56 static void
57 setup_cancellation_signals(void (*handler)(int signum))
59 apr_signal(SIGINT, handler);
60 #ifdef SIGBREAK
61 /* SIGBREAK is a Win32 specific signal generated by ctrl-break. */
62 apr_signal(SIGBREAK, handler);
63 #endif
64 #ifdef SIGHUP
65 apr_signal(SIGHUP, handler);
66 #endif
67 #ifdef SIGTERM
68 apr_signal(SIGTERM, handler);
69 #endif
73 /* Our cancellation callback. */
74 static svn_error_t *
75 check_cancel(void *baton)
77 if (cancelled)
78 return svn_error_create(SVN_ERR_CANCELLED, NULL, _("Caught signal"));
79 else
80 return SVN_NO_ERROR;
84 /* Helper to open stdio streams */
85 static svn_error_t *
86 create_stdio_stream(svn_stream_t **stream,
87 APR_DECLARE(apr_status_t) open_fn(apr_file_t **,
88 apr_pool_t *),
89 apr_pool_t *pool)
91 apr_file_t *stdio_file;
92 apr_status_t apr_err = open_fn(&stdio_file, pool);
94 if (apr_err)
95 return svn_error_wrap_apr(apr_err, _("Can't open stdio file"));
97 *stream = svn_stream_from_aprfile(stdio_file, pool);
98 return SVN_NO_ERROR;
102 /* Helper to parse local repository path. Try parsing next parameter
103 * of OS as a local path to repository. If successfull *REPOS_PATH
104 * will contain internal style path to the repository.
106 static svn_error_t *
107 parse_local_repos_path(apr_getopt_t *os,
108 const char ** repos_path,
109 apr_pool_t *pool)
111 *repos_path = NULL;
113 /* Check to see if there is one more parameter. */
114 if (os->ind < os->argc)
116 const char * path = os->argv[os->ind++];
117 SVN_ERR(svn_utf_cstring_to_utf8(repos_path, path, pool));
118 *repos_path = svn_path_internal_style(*repos_path, pool);
121 if (*repos_path == NULL)
123 return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
124 _("Repository argument required"));
126 else if (svn_path_is_url(*repos_path))
128 return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
129 _("'%s' is an URL when it should be a path"),
130 *repos_path);
133 return SVN_NO_ERROR;
137 /* Custom filesystem warning function. */
138 static void
139 warning_func(void *baton,
140 svn_error_t *err)
142 if (! err)
143 return;
144 svn_handle_error2(err, stderr, FALSE, "svnadmin: ");
148 /* Helper to open a repository and set a warning func (so we don't
149 * SEGFAULT when libsvn_fs's default handler gets run). */
150 static svn_error_t *
151 open_repos(svn_repos_t **repos,
152 const char *path,
153 apr_pool_t *pool)
155 SVN_ERR(svn_repos_open(repos, path, pool));
156 svn_fs_set_warning_func(svn_repos_fs(*repos), warning_func, NULL);
157 return SVN_NO_ERROR;
161 /* Version compatibility check */
162 static svn_error_t *
163 check_lib_versions(void)
165 static const svn_version_checklist_t checklist[] =
167 { "svn_subr", svn_subr_version },
168 { "svn_repos", svn_repos_version },
169 { "svn_fs", svn_fs_version },
170 { "svn_delta", svn_delta_version },
171 { NULL, NULL }
174 SVN_VERSION_DEFINE(my_version);
175 return svn_ver_check_list(&my_version, checklist);
180 /** Subcommands. **/
182 static svn_opt_subcommand_t
183 subcommand_crashtest,
184 subcommand_create,
185 subcommand_deltify,
186 subcommand_dump,
187 subcommand_help,
188 subcommand_hotcopy,
189 subcommand_load,
190 subcommand_list_dblogs,
191 subcommand_list_unused_dblogs,
192 subcommand_lslocks,
193 subcommand_lstxns,
194 subcommand_recover,
195 subcommand_rmlocks,
196 subcommand_rmtxns,
197 subcommand_setlog,
198 subcommand_setrevprop,
199 subcommand_setuuid,
200 subcommand_upgrade,
201 subcommand_verify;
203 enum
205 svnadmin__version = SVN_OPT_FIRST_LONGOPT_ID,
206 svnadmin__incremental,
207 svnadmin__deltas,
208 svnadmin__ignore_uuid,
209 svnadmin__force_uuid,
210 svnadmin__fs_type,
211 svnadmin__parent_dir,
212 svnadmin__bdb_txn_nosync,
213 svnadmin__bdb_log_keep,
214 svnadmin__config_dir,
215 svnadmin__bypass_hooks,
216 svnadmin__use_pre_commit_hook,
217 svnadmin__use_post_commit_hook,
218 svnadmin__use_pre_revprop_change_hook,
219 svnadmin__use_post_revprop_change_hook,
220 svnadmin__clean_logs,
221 svnadmin__wait,
222 svnadmin__pre_1_4_compatible,
223 svnadmin__pre_1_5_compatible
226 /* Option codes and descriptions.
228 * The entire list must be terminated with an entry of nulls.
230 static const apr_getopt_option_t options_table[] =
232 {"help", 'h', 0,
233 N_("show help on a subcommand")},
235 {NULL, '?', 0,
236 N_("show help on a subcommand")},
238 {"version", svnadmin__version, 0,
239 N_("show program version information")},
241 {"revision", 'r', 1,
242 N_("specify revision number ARG (or X:Y range)")},
244 {"incremental", svnadmin__incremental, 0,
245 N_("dump incrementally")},
247 {"deltas", svnadmin__deltas, 0,
248 N_("use deltas in dump output")},
250 {"bypass-hooks", svnadmin__bypass_hooks, 0,
251 N_("bypass the repository hook system")},
253 {"quiet", 'q', 0,
254 N_("no progress (only errors) to stderr")},
256 {"ignore-uuid", svnadmin__ignore_uuid, 0,
257 N_("ignore any repos UUID found in the stream")},
259 {"force-uuid", svnadmin__force_uuid, 0,
260 N_("set repos UUID to that found in stream, if any")},
262 {"fs-type", svnadmin__fs_type, 1,
263 N_("type of repository: 'fsfs' (default) or 'bdb'")},
265 {"parent-dir", svnadmin__parent_dir, 1,
266 N_("load at specified directory in repository")},
268 {"bdb-txn-nosync", svnadmin__bdb_txn_nosync, 0,
269 N_("disable fsync at transaction commit [Berkeley DB]")},
271 {"bdb-log-keep", svnadmin__bdb_log_keep, 0,
272 N_("disable automatic log file removal [Berkeley DB]")},
274 {"config-dir", svnadmin__config_dir, 1,
275 N_("read user configuration files from directory ARG")},
277 {"clean-logs", svnadmin__clean_logs, 0,
278 N_("remove redundant Berkeley DB log files\n"
279 " from source repository [Berkeley DB]")},
281 {"use-pre-commit-hook", svnadmin__use_pre_commit_hook, 0,
282 N_("call pre-commit hook before committing revisions")},
284 {"use-post-commit-hook", svnadmin__use_post_commit_hook, 0,
285 N_("call post-commit hook after committing revisions")},
287 {"use-pre-revprop-change-hook", svnadmin__use_pre_revprop_change_hook, 0,
288 N_("call hook before changing revision property")},
290 {"use-post-revprop-change-hook", svnadmin__use_post_revprop_change_hook, 0,
291 N_("call hook after changing revision property")},
293 {"wait", svnadmin__wait, 0,
294 N_("wait instead of exit if the repository is in\n"
295 " use by another process")},
297 {"pre-1.4-compatible", svnadmin__pre_1_4_compatible, 0,
298 N_("use format compatible with Subversion versions\n"
299 " earlier than 1.4")},
301 {"pre-1.5-compatible", svnadmin__pre_1_5_compatible, 0,
302 N_("use format compatible with Subversion versions\n"
303 " earlier than 1.5")},
305 {NULL}
309 /* Array of available subcommands.
310 * The entire list must be terminated with an entry of nulls.
312 static const svn_opt_subcommand_desc_t cmd_table[] =
314 {"crashtest", subcommand_crashtest, {0}, N_
315 ("usage: svnadmin crashtest REPOS_PATH\n\n"
316 "Open the repository at REPOS_PATH, then abort, thus simulating\n"
317 "a process that crashes while holding an open repository handle.\n"),
318 {0} },
320 {"create", subcommand_create, {0}, N_
321 ("usage: svnadmin create REPOS_PATH\n\n"
322 "Create a new, empty repository at REPOS_PATH.\n"),
323 {svnadmin__bdb_txn_nosync, svnadmin__bdb_log_keep,
324 svnadmin__config_dir, svnadmin__fs_type, svnadmin__pre_1_4_compatible,
325 svnadmin__pre_1_5_compatible } },
327 {"deltify", subcommand_deltify, {0}, N_
328 ("usage: svnadmin deltify [-r LOWER[:UPPER]] REPOS_PATH\n\n"
329 "Run over the requested revision range, performing predecessor delti-\n"
330 "fication on the paths changed in those revisions. Deltification in\n"
331 "essence compresses the repository by only storing the differences or\n"
332 "delta from the preceding revision. If no revisions are specified,\n"
333 "this will simply deltify the HEAD revision.\n"),
334 {'r', 'q'} },
336 {"dump", subcommand_dump, {0}, N_
337 ("usage: svnadmin dump REPOS_PATH [-r LOWER[:UPPER]] [--incremental]\n\n"
338 "Dump the contents of filesystem to stdout in a 'dumpfile'\n"
339 "portable format, sending feedback to stderr. Dump revisions\n"
340 "LOWER rev through UPPER rev. If no revisions are given, dump all\n"
341 "revision trees. If only LOWER is given, dump that one revision tree.\n"
342 "If --incremental is passed, then the first revision dumped will be\n"
343 "a diff against the previous revision, instead of the usual fulltext.\n"),
344 {'r', svnadmin__incremental, svnadmin__deltas, 'q'} },
346 {"help", subcommand_help, {"?", "h"}, N_
347 ("usage: svnadmin help [SUBCOMMAND...]\n\n"
348 "Describe the usage of this program or its subcommands.\n"),
349 {0} },
351 {"hotcopy", subcommand_hotcopy, {0}, N_
352 ("usage: svnadmin hotcopy REPOS_PATH NEW_REPOS_PATH\n\n"
353 "Makes a hot copy of a repository.\n"),
354 {svnadmin__clean_logs} },
356 {"list-dblogs", subcommand_list_dblogs, {0}, N_
357 ("usage: svnadmin list-dblogs REPOS_PATH\n\n"
358 "List all Berkeley DB log files.\n\n"
359 "WARNING: Modifying or deleting logfiles which are still in use\n"
360 "will cause your repository to be corrupted.\n"),
361 {0} },
363 {"list-unused-dblogs", subcommand_list_unused_dblogs, {0}, N_
364 ("usage: svnadmin list-unused-dblogs REPOS_PATH\n\n"
365 "List unused Berkeley DB log files.\n\n"),
366 {0} },
368 {"load", subcommand_load, {0}, N_
369 ("usage: svnadmin load REPOS_PATH\n\n"
370 "Read a 'dumpfile'-formatted stream from stdin, committing\n"
371 "new revisions into the repository's filesystem. If the repository\n"
372 "was previously empty, its UUID will, by default, be changed to the\n"
373 "one specified in the stream. Progress feedback is sent to stdout.\n"),
374 {'q', svnadmin__ignore_uuid, svnadmin__force_uuid,
375 svnadmin__use_pre_commit_hook, svnadmin__use_post_commit_hook,
376 svnadmin__parent_dir} },
378 {"lslocks", subcommand_lslocks, {0}, N_
379 ("usage: svnadmin lslocks REPOS_PATH [PATH-IN-REPOS]\n\n"
380 "Print descriptions of all locks on or under PATH-IN-REPOS (which,\n"
381 "if not provided, is the root of the repository).\n"),
382 {0} },
384 {"lstxns", subcommand_lstxns, {0}, N_
385 ("usage: svnadmin lstxns REPOS_PATH\n\n"
386 "Print the names of all uncommitted transactions.\n"),
387 {0} },
389 {"recover", subcommand_recover, {0}, N_
390 ("usage: svnadmin recover REPOS_PATH\n\n"
391 "Run the recovery procedure on a repository. Do this if you've\n"
392 "been getting errors indicating that recovery ought to be run.\n"
393 "Berkeley DB recovery requires exclusive access and will\n"
394 "exit if the repository is in use by another process.\n"),
395 {svnadmin__wait} },
397 {"rmlocks", subcommand_rmlocks, {0}, N_
398 ("usage: svnadmin rmlocks REPOS_PATH LOCKED_PATH...\n\n"
399 "Unconditionally remove lock from each LOCKED_PATH.\n"),
400 {0} },
402 {"rmtxns", subcommand_rmtxns, {0}, N_
403 ("usage: svnadmin rmtxns REPOS_PATH TXN_NAME...\n\n"
404 "Delete the named transaction(s).\n"),
405 {'q'} },
407 {"setlog", subcommand_setlog, {0}, N_
408 ("usage: svnadmin setlog REPOS_PATH -r REVISION FILE\n\n"
409 "Set the log-message on revision REVISION to the contents of FILE. Use\n"
410 "--bypass-hooks to avoid triggering the revision-property-related hooks\n"
411 "(for example, if you do not want an email notification sent\n"
412 "from your post-revprop-change hook, or because the modification of\n"
413 "revision properties has not been enabled in the pre-revprop-change\n"
414 "hook).\n\n"
415 "NOTE: Revision properties are not versioned, so this command will\n"
416 "overwrite the previous log message.\n"),
417 {'r', svnadmin__bypass_hooks} },
419 {"setrevprop", subcommand_setrevprop, {0}, N_
420 ("usage: svnadmin setrevprop REPOS_PATH -r REVISION NAME FILE\n\n"
421 "Set the property NAME on revision REVISION to the contents of FILE. Use\n"
422 "--use-pre-revprop-change-hook/--use-post-revprop-change-hook to trigger\n"
423 "the revision property-related hooks (for example, if you want an email\n"
424 "notification sent from your post-revprop-change hook).\n\n"
425 "NOTE: Revision properties are not versioned, so this command will\n"
426 "overwrite the previous value of the property.\n"),
427 {'r', svnadmin__use_pre_revprop_change_hook,
428 svnadmin__use_post_revprop_change_hook} },
430 {"setuuid", subcommand_setuuid, {0}, N_
431 ("usage: svnadmin setuuid REPOS_PATH [NEW_UUID]\n\n"
432 "Reset the repository UUID for the repository located at REPOS_PATH. If\n"
433 "NEW_UUID is provided, use that as the new repository UUID; otherwise,\n"
434 "generate a brand new UUID for the repository.\n"),
435 {0} },
437 {"upgrade", subcommand_upgrade, {0}, N_
438 ("usage: svnadmin upgrade REPOS_PATH\n\n"
439 "Upgrade the repository located at REPOS_PATH to the latest supported\n"
440 "schema version.\n\n"
441 "This functionality is provided as a convenience for repository\n"
442 "administrators who wish to make use of new Subversion functionality\n"
443 "without having to undertake a potentially costly full repository dump\n"
444 "and load operation. As such, the upgrade performs only the minimum\n"
445 "amount of work needed to accomplish this while still maintaining the\n"
446 "integrity of the repository. It does not guarantee the most optimized\n"
447 "repository state as a dump and subsequent load would.\n"),
448 {0} },
450 {"verify", subcommand_verify, {0}, N_
451 ("usage: svnadmin verify REPOS_PATH\n\n"
452 "Verifies the data stored in the repository.\n"),
453 {'r', 'q'} },
455 { NULL, NULL, {0}, NULL, {0} }
459 /* Baton for passing option/argument state to a subcommand function. */
460 struct svnadmin_opt_state
462 const char *repository_path;
463 const char *new_repository_path; /* hotcopy dest. path */
464 const char *fs_type; /* --fs-type */
465 svn_boolean_t pre_1_4_compatible; /* --pre-1.4-compatible */
466 svn_boolean_t pre_1_5_compatible; /* --pre-1.5-compatible */
467 svn_opt_revision_t start_revision, end_revision; /* -r X[:Y] */
468 svn_boolean_t help; /* --help or -? */
469 svn_boolean_t version; /* --version */
470 svn_boolean_t incremental; /* --incremental */
471 svn_boolean_t use_deltas; /* --deltas */
472 svn_boolean_t use_pre_commit_hook; /* --use-pre-commit-hook */
473 svn_boolean_t use_post_commit_hook; /* --use-post-commit-hook */
474 svn_boolean_t use_pre_revprop_change_hook; /* --use-pre-revprop-change-hook */
475 svn_boolean_t use_post_revprop_change_hook; /* --use-post-revprop-change-hook */
476 svn_boolean_t quiet; /* --quiet */
477 svn_boolean_t bdb_txn_nosync; /* --bdb-txn-nosync */
478 svn_boolean_t bdb_log_keep; /* --bdb-log-keep */
479 svn_boolean_t clean_logs; /* --clean-logs */
480 svn_boolean_t bypass_hooks; /* --bypass-hooks */
481 svn_boolean_t wait; /* --wait */
482 enum svn_repos_load_uuid uuid_action; /* --ignore-uuid,
483 --force-uuid */
484 const char *parent_dir;
486 const char *config_dir; /* Overriding Configuration Directory */
490 /* Set *REVNUM to the revision specified by REVISION (or to
491 SVN_INVALID_REVNUM if that has the type 'unspecified'),
492 possibly making use of the YOUNGEST revision number in REPOS. */
493 static svn_error_t *
494 get_revnum(svn_revnum_t *revnum, const svn_opt_revision_t *revision,
495 svn_revnum_t youngest, svn_repos_t *repos, apr_pool_t *pool)
497 if (revision->kind == svn_opt_revision_number)
498 *revnum = revision->value.number;
499 else if (revision->kind == svn_opt_revision_head)
500 *revnum = youngest;
501 else if (revision->kind == svn_opt_revision_date)
502 SVN_ERR(svn_repos_dated_revision
503 (revnum, repos, revision->value.date, pool));
504 else if (revision->kind == svn_opt_revision_unspecified)
505 *revnum = SVN_INVALID_REVNUM;
506 else
507 return svn_error_create
508 (SVN_ERR_CL_ARG_PARSING_ERROR, NULL, _("Invalid revision specifier"));
510 if (*revnum > youngest)
511 return svn_error_createf
512 (SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
513 _("Revisions must not be greater than the youngest revision (%ld)"),
514 youngest);
516 return SVN_NO_ERROR;
520 /* This implements `svn_opt_subcommand_t'. */
521 static svn_error_t *
522 subcommand_create(apr_getopt_t *os, void *baton, apr_pool_t *pool)
524 struct svnadmin_opt_state *opt_state = baton;
525 svn_repos_t *repos;
526 apr_hash_t *config;
527 apr_hash_t *fs_config = apr_hash_make(pool);
529 apr_hash_set(fs_config, SVN_FS_CONFIG_BDB_TXN_NOSYNC,
530 APR_HASH_KEY_STRING,
531 (opt_state->bdb_txn_nosync ? "1" : "0"));
533 apr_hash_set(fs_config, SVN_FS_CONFIG_BDB_LOG_AUTOREMOVE,
534 APR_HASH_KEY_STRING,
535 (opt_state->bdb_log_keep ? "0" : "1"));
537 if (opt_state->fs_type)
538 apr_hash_set(fs_config, SVN_FS_CONFIG_FS_TYPE,
539 APR_HASH_KEY_STRING,
540 opt_state->fs_type);
542 if (opt_state->pre_1_4_compatible)
543 apr_hash_set(fs_config, SVN_FS_CONFIG_PRE_1_4_COMPATIBLE,
544 APR_HASH_KEY_STRING,
545 "1");
547 if (opt_state->pre_1_5_compatible)
548 apr_hash_set(fs_config, SVN_FS_CONFIG_PRE_1_5_COMPATIBLE,
549 APR_HASH_KEY_STRING,
550 "1");
552 SVN_ERR(svn_config_get_config(&config, opt_state->config_dir, pool));
553 SVN_ERR(svn_repos_create(&repos, opt_state->repository_path,
554 NULL, NULL,
555 config, fs_config, pool));
556 svn_fs_set_warning_func(svn_repos_fs(repos), warning_func, NULL);
557 return SVN_NO_ERROR;
561 /* This implements `svn_opt_subcommand_t'. */
562 static svn_error_t *
563 subcommand_deltify(apr_getopt_t *os, void *baton, apr_pool_t *pool)
565 struct svnadmin_opt_state *opt_state = baton;
566 svn_repos_t *repos;
567 svn_fs_t *fs;
568 svn_revnum_t start = SVN_INVALID_REVNUM, end = SVN_INVALID_REVNUM;
569 svn_revnum_t youngest, revision;
570 apr_pool_t *subpool = svn_pool_create(pool);
572 SVN_ERR(open_repos(&repos, opt_state->repository_path, pool));
573 fs = svn_repos_fs(repos);
574 SVN_ERR(svn_fs_youngest_rev(&youngest, fs, pool));
576 /* Find the revision numbers at which to start and end. */
577 SVN_ERR(get_revnum(&start, &opt_state->start_revision,
578 youngest, repos, pool));
579 SVN_ERR(get_revnum(&end, &opt_state->end_revision,
580 youngest, repos, pool));
582 /* Fill in implied revisions if necessary. */
583 if (start == SVN_INVALID_REVNUM)
584 start = youngest;
585 if (end == SVN_INVALID_REVNUM)
586 end = start;
588 if (start > end)
589 return svn_error_create
590 (SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
591 _("First revision cannot be higher than second"));
593 /* Loop over the requested revision range, performing the
594 predecessor deltification on paths changed in each. */
595 for (revision = start; revision <= end; revision++)
597 svn_pool_clear(subpool);
598 SVN_ERR(check_cancel(NULL));
599 if (! opt_state->quiet)
600 SVN_ERR(svn_cmdline_printf(subpool, _("Deltifying revision %ld..."),
601 revision));
602 SVN_ERR(svn_fs_deltify_revision(fs, revision, subpool));
603 if (! opt_state->quiet)
604 SVN_ERR(svn_cmdline_printf(subpool, _("done.\n")));
606 svn_pool_destroy(subpool);
608 return SVN_NO_ERROR;
612 /* Baton for recode_write(). */
613 struct recode_write_baton
615 apr_pool_t *pool;
616 FILE *out;
619 /* This implements the 'svn_write_fn_t' interface.
621 Write DATA to ((struct recode_write_baton *) BATON)->out, in the
622 console encoding, using svn_cmdline_fprintf(). DATA is a
623 UTF8-encoded C string, therefore ignore LEN.
625 ### This recoding mechanism might want to be abstracted into
626 ### svn_io.h or svn_cmdline.h, if it proves useful elsewhere. */
627 static svn_error_t *recode_write(void *baton,
628 const char *data,
629 apr_size_t *len)
631 struct recode_write_baton *rwb = baton;
632 svn_pool_clear(rwb->pool);
633 return svn_cmdline_fputs(data, rwb->out, rwb->pool);
636 /* Create a stream, to write to STD_STREAM, that uses recode_write()
637 to perform UTF-8 to console encoding translation. */
638 static svn_stream_t *
639 recode_stream_create(FILE *std_stream, apr_pool_t *pool)
641 struct recode_write_baton *std_stream_rwb =
642 apr_palloc(pool, sizeof(struct recode_write_baton));
644 svn_stream_t *rw_stream = svn_stream_create(std_stream_rwb, pool);
645 std_stream_rwb->pool = svn_pool_create(pool);
646 std_stream_rwb->out = std_stream;
647 svn_stream_set_write(rw_stream, recode_write);
648 return rw_stream;
652 /* This implements `svn_opt_subcommand_t'. */
653 static svn_error_t *
654 subcommand_dump(apr_getopt_t *os, void *baton, apr_pool_t *pool)
656 struct svnadmin_opt_state *opt_state = baton;
657 svn_repos_t *repos;
658 svn_fs_t *fs;
659 svn_stream_t *stdout_stream, *stderr_stream = NULL;
660 svn_revnum_t lower = SVN_INVALID_REVNUM, upper = SVN_INVALID_REVNUM;
661 svn_revnum_t youngest;
663 SVN_ERR(open_repos(&repos, opt_state->repository_path, pool));
664 fs = svn_repos_fs(repos);
665 SVN_ERR(svn_fs_youngest_rev(&youngest, fs, pool));
667 /* Find the revision numbers at which to start and end. */
668 SVN_ERR(get_revnum(&lower, &opt_state->start_revision,
669 youngest, repos, pool));
670 SVN_ERR(get_revnum(&upper, &opt_state->end_revision,
671 youngest, repos, pool));
673 /* Fill in implied revisions if necessary. */
674 if (lower == SVN_INVALID_REVNUM)
676 lower = 0;
677 upper = youngest;
679 else if (upper == SVN_INVALID_REVNUM)
681 upper = lower;
684 if (lower > upper)
685 return svn_error_create
686 (SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
687 _("First revision cannot be higher than second"));
689 SVN_ERR(create_stdio_stream(&stdout_stream, apr_file_open_stdout, pool));
691 /* Progress feedback goes to STDERR, unless they asked to suppress it. */
692 if (! opt_state->quiet)
693 stderr_stream = recode_stream_create(stderr, pool);
695 SVN_ERR(svn_repos_dump_fs2(repos, stdout_stream, stderr_stream,
696 lower, upper, opt_state->incremental,
697 opt_state->use_deltas, check_cancel, NULL,
698 pool));
700 return SVN_NO_ERROR;
704 /* This implements `svn_opt_subcommand_t'. */
705 static svn_error_t *
706 subcommand_help(apr_getopt_t *os, void *baton, apr_pool_t *pool)
708 struct svnadmin_opt_state *opt_state = baton;
709 const char *header =
710 _("general usage: svnadmin SUBCOMMAND REPOS_PATH [ARGS & OPTIONS ...]\n"
711 "Type 'svnadmin help <subcommand>' for help on a specific subcommand.\n"
712 "Type 'svnadmin --version' to see the program version and FS modules.\n"
713 "\n"
714 "Available subcommands:\n");
716 const char *fs_desc_start
717 = _("The following repository back-end (FS) modules are available:\n\n");
719 svn_stringbuf_t *version_footer;
721 version_footer = svn_stringbuf_create(fs_desc_start, pool);
722 SVN_ERR(svn_fs_print_modules(version_footer, pool));
724 SVN_ERR(svn_opt_print_help(os, "svnadmin",
725 opt_state ? opt_state->version : FALSE,
726 FALSE, version_footer->data,
727 header, cmd_table, options_table, NULL,
728 pool));
730 return SVN_NO_ERROR;
734 /* This implements `svn_opt_subcommand_t'. */
735 static svn_error_t *
736 subcommand_load(apr_getopt_t *os, void *baton, apr_pool_t *pool)
738 struct svnadmin_opt_state *opt_state = baton;
739 svn_repos_t *repos;
740 svn_stream_t *stdin_stream, *stdout_stream = NULL;
742 SVN_ERR(open_repos(&repos, opt_state->repository_path, pool));
744 /* Read the stream from STDIN. Users can redirect a file. */
745 SVN_ERR(create_stdio_stream(&stdin_stream,
746 apr_file_open_stdin, pool));
748 /* Progress feedback goes to STDOUT, unless they asked to suppress it. */
749 if (! opt_state->quiet)
750 stdout_stream = recode_stream_create(stdout, pool);
752 SVN_ERR(svn_repos_load_fs2(repos, stdin_stream, stdout_stream,
753 opt_state->uuid_action, opt_state->parent_dir,
754 opt_state->use_pre_commit_hook,
755 opt_state->use_post_commit_hook,
756 check_cancel, NULL, pool));
758 return SVN_NO_ERROR;
762 /* This implements `svn_opt_subcommand_t'. */
763 static svn_error_t *
764 subcommand_lstxns(apr_getopt_t *os, void *baton, apr_pool_t *pool)
766 struct svnadmin_opt_state *opt_state = baton;
767 svn_repos_t *repos;
768 svn_fs_t *fs;
769 apr_array_header_t *txns;
770 int i;
772 SVN_ERR(open_repos(&repos, opt_state->repository_path, pool));
773 fs = svn_repos_fs(repos);
774 SVN_ERR(svn_fs_list_transactions(&txns, fs, pool));
776 /* Loop, printing revisions. */
777 for (i = 0; i < txns->nelts; i++)
779 SVN_ERR(svn_cmdline_printf(pool, "%s\n",
780 APR_ARRAY_IDX(txns, i, const char *)));
783 return SVN_NO_ERROR;
787 /* A callback which is called when the recovery starts. */
788 static svn_error_t *
789 recovery_started(void *baton)
791 apr_pool_t *pool = (apr_pool_t *)baton;
793 SVN_ERR(svn_cmdline_printf(pool,
794 _("Repository lock acquired.\n"
795 "Please wait; recovering the"
796 " repository may take some time...\n")));
797 SVN_ERR(svn_cmdline_fflush(stdout));
799 /* Enable cancellation signal handlers. */
800 setup_cancellation_signals(signal_handler);
802 return SVN_NO_ERROR;
806 /* This implements `svn_opt_subcommand_t'. */
807 static svn_error_t *
808 subcommand_recover(apr_getopt_t *os, void *baton, apr_pool_t *pool)
810 svn_revnum_t youngest_rev;
811 svn_repos_t *repos;
812 svn_error_t *err;
813 struct svnadmin_opt_state *opt_state = baton;
815 /* Restore default signal handlers until after we have acquired the
816 * exclusive lock so that the user interrupt before we actually
817 * touch the repository. */
818 setup_cancellation_signals(SIG_DFL);
820 err = svn_repos_recover3(opt_state->repository_path, TRUE,
821 recovery_started, pool,
822 check_cancel, NULL, pool);
823 if (err)
825 if (! APR_STATUS_IS_EAGAIN(err->apr_err))
826 return err;
827 svn_error_clear(err);
828 if (! opt_state->wait)
829 return svn_error_create(SVN_ERR_REPOS_LOCKED, NULL,
830 _("Failed to get exclusive repository "
831 "access; perhaps another process\n"
832 "such as httpd, svnserve or svn "
833 "has it open?"));
834 SVN_ERR(svn_cmdline_printf(pool,
835 _("Waiting on repository lock; perhaps"
836 " another process has it open?\n")));
837 SVN_ERR(svn_cmdline_fflush(stdout));
838 SVN_ERR(svn_repos_recover3(opt_state->repository_path, FALSE,
839 recovery_started, pool,
840 check_cancel, NULL, pool));
843 SVN_ERR(svn_cmdline_printf(pool, _("\nRecovery completed.\n")));
845 /* Since db transactions may have been replayed, it's nice to tell
846 people what the latest revision is. It also proves that the
847 recovery actually worked. */
848 SVN_ERR(open_repos(&repos, opt_state->repository_path, pool));
849 SVN_ERR(svn_fs_youngest_rev(&youngest_rev, svn_repos_fs(repos), pool));
850 SVN_ERR(svn_cmdline_printf(pool, _("The latest repos revision is %ld.\n"),
851 youngest_rev));
853 return SVN_NO_ERROR;
857 /* This implements `svn_opt_subcommand_t'. */
858 static svn_error_t *
859 list_dblogs(apr_getopt_t *os, void *baton, svn_boolean_t only_unused,
860 apr_pool_t *pool)
862 struct svnadmin_opt_state *opt_state = baton;
863 apr_array_header_t *logfiles;
864 int i;
866 SVN_ERR(svn_repos_db_logfiles(&logfiles,
867 opt_state->repository_path,
868 only_unused,
869 pool));
871 /* Loop, printing log files. We append the log paths to the
872 repository path, making sure to return everything to the native
873 style before printing. */
874 for (i = 0; i < logfiles->nelts; i++)
876 const char *log_utf8;
877 log_utf8 = svn_path_join(opt_state->repository_path,
878 APR_ARRAY_IDX(logfiles, i, const char *),
879 pool);
880 log_utf8 = svn_path_local_style(log_utf8, pool);
881 SVN_ERR(svn_cmdline_printf(pool, "%s\n", log_utf8));
884 return SVN_NO_ERROR;
888 /* This implements `svn_opt_subcommand_t'. */
889 static svn_error_t *
890 subcommand_list_dblogs(apr_getopt_t *os, void *baton, apr_pool_t *pool)
892 SVN_ERR(list_dblogs(os, baton, FALSE, pool));
893 return SVN_NO_ERROR;
897 /* This implements `svn_opt_subcommand_t'. */
898 static svn_error_t *
899 subcommand_list_unused_dblogs(apr_getopt_t *os, void *baton, apr_pool_t *pool)
901 SVN_ERR(list_dblogs(os, baton, TRUE, pool));
902 return SVN_NO_ERROR;
906 /* This implements `svn_opt_subcommand_t'. */
907 static svn_error_t *
908 subcommand_rmtxns(apr_getopt_t *os, void *baton, apr_pool_t *pool)
910 struct svnadmin_opt_state *opt_state = baton;
911 svn_repos_t *repos;
912 svn_fs_t *fs;
913 svn_fs_txn_t *txn;
914 apr_array_header_t *args;
915 int i;
916 apr_pool_t *subpool = svn_pool_create(pool);
918 SVN_ERR(open_repos(&repos, opt_state->repository_path, pool));
919 fs = svn_repos_fs(repos);
921 SVN_ERR(svn_opt_parse_all_args(&args, os, pool));
923 /* All the rest of the arguments are transaction names. */
924 for (i = 0; i < args->nelts; i++)
926 const char *txn_name = APR_ARRAY_IDX(args, i, const char *);
927 const char *txn_name_utf8;
928 svn_error_t *err;
930 svn_pool_clear(subpool);
932 SVN_ERR(svn_utf_cstring_to_utf8(&txn_name_utf8, txn_name, subpool));
934 /* Try to open the txn. If that succeeds, try to abort it. */
935 err = svn_fs_open_txn(&txn, fs, txn_name_utf8, subpool);
936 if (! err)
937 err = svn_fs_abort_txn(txn, subpool);
939 /* If either the open or the abort of the txn fails because that
940 transaction is dead, just try to purge the thing. Else,
941 there was either an error worth reporting, or not error at
942 all. */
943 if (err && (err->apr_err == SVN_ERR_FS_TRANSACTION_DEAD))
945 svn_error_clear(err);
946 err = svn_fs_purge_txn(fs, txn_name_utf8, subpool);
949 /* If we had a real from the txn open, abort, or purge, we clear
950 that error and just report to the user that we had an issue
951 with this particular txn. */
952 if (err)
954 svn_handle_error2(err, stderr, FALSE /* non-fatal */, "svnadmin: ");
955 svn_error_clear(err);
957 else if (! opt_state->quiet)
959 SVN_ERR
960 (svn_cmdline_printf(subpool, _("Transaction '%s' removed.\n"),
961 txn_name));
965 svn_pool_destroy(subpool);
967 return SVN_NO_ERROR;
971 /* A helper for the 'setrevprop' and 'setlog' commands. Expects
972 OPT_STATE->use_pre_revprop_change_hook and
973 OPT_STATE->use_post_revprop_change_hook to be set appropriately. */
974 static svn_error_t *
975 set_revprop(const char *prop_name, const char *filename,
976 struct svnadmin_opt_state *opt_state, apr_pool_t *pool)
978 svn_repos_t *repos;
979 svn_string_t *prop_value = svn_string_create("", pool);
980 svn_stringbuf_t *file_contents;
981 const char *filename_utf8;
983 SVN_ERR(svn_utf_cstring_to_utf8(&filename_utf8, filename, pool));
984 filename_utf8 = svn_path_internal_style(filename_utf8, pool);
986 SVN_ERR(svn_stringbuf_from_file(&file_contents, filename_utf8, pool));
988 prop_value->data = file_contents->data;
989 prop_value->len = file_contents->len;
991 SVN_ERR(svn_subst_translate_string(&prop_value, prop_value, NULL, pool));
993 /* Open the filesystem */
994 SVN_ERR(open_repos(&repos, opt_state->repository_path, pool));
996 /* If we are bypassing the hooks system, we just hit the filesystem
997 directly. */
998 if (opt_state->use_pre_revprop_change_hook ||
999 opt_state->use_post_revprop_change_hook)
1001 SVN_ERR(svn_repos_fs_change_rev_prop3
1002 (repos, opt_state->start_revision.value.number,
1003 NULL, prop_name, prop_value,
1004 opt_state->use_pre_revprop_change_hook,
1005 opt_state->use_post_revprop_change_hook, NULL, NULL, pool));
1007 else
1009 svn_fs_t *fs = svn_repos_fs(repos);
1010 SVN_ERR(svn_fs_change_rev_prop
1011 (fs, opt_state->start_revision.value.number,
1012 prop_name, prop_value, pool));
1015 return SVN_NO_ERROR;
1019 /* This implements `svn_opt_subcommand_t'. */
1020 static svn_error_t *
1021 subcommand_setrevprop(apr_getopt_t *os, void *baton, apr_pool_t *pool)
1023 struct svnadmin_opt_state *opt_state = baton;
1024 apr_array_header_t *args;
1026 if (opt_state->start_revision.kind != svn_opt_revision_number)
1027 return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
1028 _("Missing revision"));
1029 else if (opt_state->end_revision.kind != svn_opt_revision_unspecified)
1030 return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
1031 _("Only one revision allowed"));
1033 SVN_ERR(svn_opt_parse_all_args(&args, os, pool));
1035 if (args->nelts != 2)
1036 return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
1037 _("Exactly one property name and one file "
1038 "argument required"));
1040 return set_revprop(APR_ARRAY_IDX(args, 0, const char *),
1041 APR_ARRAY_IDX(args, 1, const char *),
1042 opt_state, pool);
1046 /* This implements `svn_opt_subcommand_t'. */
1047 static svn_error_t *
1048 subcommand_setuuid(apr_getopt_t *os, void *baton, apr_pool_t *pool)
1050 struct svnadmin_opt_state *opt_state = baton;
1051 apr_array_header_t *args;
1052 svn_repos_t *repos;
1053 svn_fs_t *fs;
1054 const char *uuid = NULL;
1056 SVN_ERR(svn_opt_parse_all_args(&args, os, pool));
1058 if (args->nelts > 1)
1059 return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL, NULL);
1060 if (args->nelts == 1)
1061 uuid = APR_ARRAY_IDX(args, 0, const char *);
1063 SVN_ERR(open_repos(&repos, opt_state->repository_path, pool));
1064 fs = svn_repos_fs(repos);
1065 return svn_fs_set_uuid(fs, uuid, pool);
1069 /* This implements `svn_opt_subcommand_t'. */
1070 static svn_error_t *
1071 subcommand_setlog(apr_getopt_t *os, void *baton, apr_pool_t *pool)
1073 struct svnadmin_opt_state *opt_state = baton;
1074 apr_array_header_t *args;
1076 if (opt_state->start_revision.kind != svn_opt_revision_number)
1077 return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
1078 _("Missing revision"));
1079 else if (opt_state->end_revision.kind != svn_opt_revision_unspecified)
1080 return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
1081 _("Only one revision allowed"));
1083 SVN_ERR(svn_opt_parse_all_args(&args, os, pool));
1085 if (args->nelts != 1)
1086 return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
1087 _("Exactly one file argument required"));
1089 /* set_revprop() responds only to pre-/post-revprop-change opts. */
1090 if (!opt_state->bypass_hooks)
1092 opt_state->use_pre_revprop_change_hook = TRUE;
1093 opt_state->use_post_revprop_change_hook = TRUE;
1096 return set_revprop(SVN_PROP_REVISION_LOG,
1097 APR_ARRAY_IDX(args, 0, const char *),
1098 opt_state, pool);
1102 /* This implements `svn_opt_subcommand_t'. */
1103 static svn_error_t *
1104 subcommand_verify(apr_getopt_t *os, void *baton, apr_pool_t *pool)
1106 struct svnadmin_opt_state *opt_state = baton;
1107 svn_stream_t *stderr_stream;
1108 svn_repos_t *repos;
1109 svn_fs_t *fs;
1110 svn_revnum_t youngest, lower, upper;
1112 SVN_ERR(open_repos(&repos, opt_state->repository_path, pool));
1113 fs = svn_repos_fs(repos);
1114 SVN_ERR(svn_fs_youngest_rev(&youngest, fs, pool));
1116 /* Find the revision numbers at which to start and end. */
1117 SVN_ERR(get_revnum(&lower, &opt_state->start_revision,
1118 youngest, repos, pool));
1119 SVN_ERR(get_revnum(&upper, &opt_state->end_revision,
1120 youngest, repos, pool));
1122 if (upper == SVN_INVALID_REVNUM)
1124 upper = lower;
1127 if (opt_state->quiet)
1128 stderr_stream = NULL;
1129 else
1130 stderr_stream = recode_stream_create(stderr, pool);
1132 SVN_ERR(open_repos(&repos, opt_state->repository_path, pool));
1133 return svn_repos_verify_fs(repos, stderr_stream, lower, upper,
1134 check_cancel, NULL, pool);
1138 /* This implements `svn_opt_subcommand_t'. */
1139 svn_error_t *
1140 subcommand_hotcopy(apr_getopt_t *os, void *baton, apr_pool_t *pool)
1142 struct svnadmin_opt_state *opt_state = baton;
1144 SVN_ERR(svn_repos_hotcopy(opt_state->repository_path,
1145 opt_state->new_repository_path,
1146 opt_state->clean_logs,
1147 pool));
1149 return SVN_NO_ERROR;
1153 static svn_error_t *
1154 subcommand_lslocks(apr_getopt_t *os, void *baton, apr_pool_t *pool)
1156 struct svnadmin_opt_state *opt_state = baton;
1157 apr_array_header_t *targets;
1158 svn_repos_t *repos;
1159 const char *fs_path = "/";
1160 svn_fs_t *fs;
1161 apr_hash_t *locks;
1162 apr_hash_index_t *hi;
1164 SVN_ERR(svn_opt_args_to_target_array2(&targets, os,
1165 apr_array_make(pool, 0,
1166 sizeof(const char *)),
1167 pool));
1168 if (targets->nelts > 1)
1169 return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, 0, NULL);
1170 if (targets->nelts)
1171 fs_path = APR_ARRAY_IDX(targets, 0, const char *);
1173 SVN_ERR(open_repos(&repos, opt_state->repository_path, pool));
1174 fs = svn_repos_fs(repos);
1176 /* Fetch all locks on or below the root directory. */
1177 SVN_ERR(svn_repos_fs_get_locks(&locks, repos, fs_path, NULL, NULL, pool));
1179 for (hi = apr_hash_first(pool, locks); hi; hi = apr_hash_next(hi))
1181 const void *key;
1182 void *val;
1183 const char *path, *cr_date, *exp_date = "";
1184 svn_lock_t *lock;
1185 int comment_lines = 0;
1187 apr_hash_this(hi, &key, NULL, &val);
1188 path = key;
1189 lock = val;
1191 cr_date = svn_time_to_human_cstring(lock->creation_date, pool);
1193 if (lock->expiration_date)
1194 exp_date = svn_time_to_human_cstring(lock->expiration_date, pool);
1196 if (lock->comment)
1197 comment_lines = svn_cstring_count_newlines(lock->comment) + 1;
1199 SVN_ERR(svn_cmdline_printf(pool, _("Path: %s\n"), path));
1200 SVN_ERR(svn_cmdline_printf(pool, _("UUID Token: %s\n"), lock->token));
1201 SVN_ERR(svn_cmdline_printf(pool, _("Owner: %s\n"), lock->owner));
1202 SVN_ERR(svn_cmdline_printf(pool, _("Created: %s\n"), cr_date));
1203 SVN_ERR(svn_cmdline_printf(pool, _("Expires: %s\n"), exp_date));
1204 SVN_ERR(svn_cmdline_printf(pool, (comment_lines != 1)
1205 ? _("Comment (%i lines):\n%s\n\n")
1206 : _("Comment (%i line):\n%s\n\n"),
1207 comment_lines,
1208 lock->comment ? lock->comment : ""));
1211 return SVN_NO_ERROR;
1216 static svn_error_t *
1217 subcommand_rmlocks(apr_getopt_t *os, void *baton, apr_pool_t *pool)
1219 struct svnadmin_opt_state *opt_state = baton;
1220 svn_repos_t *repos;
1221 svn_fs_t *fs;
1222 svn_fs_access_t *access;
1223 svn_error_t *err;
1224 apr_array_header_t *args;
1225 int i;
1226 const char *username;
1227 apr_pool_t *subpool = svn_pool_create(pool);
1229 SVN_ERR(open_repos(&repos, opt_state->repository_path, pool));
1230 fs = svn_repos_fs(repos);
1232 /* svn_fs_unlock() demands that some username be associated with the
1233 filesystem, so just use the UID of the person running 'svnadmin'.*/
1234 username = svn_user_get_name(pool);
1235 if (! username)
1236 username = "administrator";
1238 /* Create an access context describing the current user. */
1239 SVN_ERR(svn_fs_create_access(&access, username, pool));
1241 /* Attach the access context to the filesystem. */
1242 SVN_ERR(svn_fs_set_access(fs, access));
1244 /* Parse out any options. */
1245 SVN_ERR(svn_opt_parse_all_args(&args, os, pool));
1247 /* Our usage requires at least one FS path. */
1248 if (args->nelts == 0)
1249 return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, 0,
1250 _("No paths to unlock provided"));
1252 /* All the rest of the arguments are paths from which to remove locks. */
1253 for (i = 0; i < args->nelts; i++)
1255 const char *lock_path = APR_ARRAY_IDX(args, i, const char *);
1256 const char *lock_path_utf8;
1257 svn_lock_t *lock;
1259 SVN_ERR(svn_utf_cstring_to_utf8(&lock_path_utf8, lock_path, subpool));
1261 /* Fetch the path's svn_lock_t. */
1262 err = svn_fs_get_lock(&lock, fs, lock_path_utf8, subpool);
1263 if (err)
1264 goto move_on;
1265 if (! lock)
1267 SVN_ERR(svn_cmdline_printf(subpool,
1268 _("Path '%s' isn't locked.\n"),
1269 lock_path));
1270 continue;
1273 /* Now forcibly destroy the lock. */
1274 err = svn_fs_unlock(fs, lock_path_utf8,
1275 lock->token, 1 /* force */, subpool);
1276 if (err)
1277 goto move_on;
1279 SVN_ERR(svn_cmdline_printf(subpool,
1280 _("Removed lock on '%s'.\n"), lock->path));
1282 move_on:
1283 if (err)
1285 /* Print the error, but move on to the next lock. */
1286 svn_handle_error2(err, stderr, FALSE /* non-fatal */, "svnadmin: ");
1287 svn_error_clear(err);
1290 svn_pool_clear(subpool);
1293 svn_pool_destroy(subpool);
1294 return SVN_NO_ERROR;
1298 /* A callback which is called when the upgrade starts. */
1299 static svn_error_t *
1300 upgrade_started(void *baton)
1302 apr_pool_t *pool = (apr_pool_t *)baton;
1304 SVN_ERR(svn_cmdline_printf(pool,
1305 _("Repository lock acquired.\n"
1306 "Please wait; upgrading the"
1307 " repository may take some time...\n")));
1308 SVN_ERR(svn_cmdline_fflush(stdout));
1310 /* Enable cancellation signal handlers. */
1311 setup_cancellation_signals(signal_handler);
1313 return SVN_NO_ERROR;
1317 /* This implements `svn_opt_subcommand_t'. */
1318 static svn_error_t *
1319 subcommand_upgrade(apr_getopt_t *os, void *baton, apr_pool_t *pool)
1321 svn_error_t *err;
1322 struct svnadmin_opt_state *opt_state = baton;
1324 /* Restore default signal handlers. */
1325 setup_cancellation_signals(SIG_DFL);
1327 err = svn_repos_upgrade(opt_state->repository_path, TRUE,
1328 upgrade_started, pool, pool);
1329 if (err)
1331 if (APR_STATUS_IS_EAGAIN(err->apr_err))
1333 svn_error_clear(err);
1334 err = SVN_NO_ERROR;
1335 if (! opt_state->wait)
1336 return svn_error_create(SVN_ERR_REPOS_LOCKED, NULL,
1337 _("Failed to get exclusive repository "
1338 "access; perhaps another process\n"
1339 "such as httpd, svnserve or svn "
1340 "has it open?"));
1341 SVN_ERR(svn_cmdline_printf(pool,
1342 _("Waiting on repository lock; perhaps"
1343 " another process has it open?\n")));
1344 SVN_ERR(svn_cmdline_fflush(stdout));
1345 SVN_ERR(svn_repos_upgrade(opt_state->repository_path, FALSE,
1346 upgrade_started, pool, pool));
1348 else if (err->apr_err == SVN_ERR_FS_UNSUPPORTED_UPGRADE)
1350 return svn_error_quick_wrap
1351 (err, _("Upgrade of this repository's underlying versioned "
1352 "filesystem is not supported; consider "
1353 "dumping and loading the data elsewhere"));
1355 else if (err->apr_err == SVN_ERR_REPOS_UNSUPPORTED_UPGRADE)
1357 return svn_error_quick_wrap
1358 (err, _("Upgrade of this repository is not supported; consider "
1359 "dumping and loading the data elsewhere"));
1362 SVN_ERR(err);
1364 SVN_ERR(svn_cmdline_printf(pool, _("\nUpgrade completed.\n")));
1365 return SVN_NO_ERROR;
1370 /** Main. **/
1373 main(int argc, const char *argv[])
1375 svn_error_t *err;
1376 apr_status_t apr_err;
1377 apr_allocator_t *allocator;
1378 apr_pool_t *pool;
1380 const svn_opt_subcommand_desc_t *subcommand = NULL;
1381 struct svnadmin_opt_state opt_state;
1382 apr_getopt_t *os;
1383 int opt_id;
1384 apr_array_header_t *received_opts;
1385 int i;
1387 /* Initialize the app. */
1388 if (svn_cmdline_init("svnadmin", stderr) != EXIT_SUCCESS)
1389 return EXIT_FAILURE;
1391 /* Create our top-level pool. Use a seperate mutexless allocator,
1392 * given this application is single threaded.
1394 if (apr_allocator_create(&allocator))
1395 return EXIT_FAILURE;
1397 apr_allocator_max_free_set(allocator, SVN_ALLOCATOR_RECOMMENDED_MAX_FREE);
1399 pool = svn_pool_create_ex(NULL, allocator);
1400 apr_allocator_owner_set(allocator, pool);
1402 received_opts = apr_array_make(pool, SVN_OPT_MAX_OPTIONS, sizeof(int));
1404 /* Check library versions */
1405 err = check_lib_versions();
1406 if (err)
1407 return svn_cmdline_handle_exit_error(err, pool, "svnadmin: ");
1409 /* Initialize the FS library. */
1410 err = svn_fs_initialize(pool);
1411 if (err)
1412 return svn_cmdline_handle_exit_error(err, pool, "svnadmin: ");
1414 if (argc <= 1)
1416 subcommand_help(NULL, NULL, pool);
1417 svn_pool_destroy(pool);
1418 return EXIT_FAILURE;
1421 /* Initialize opt_state. */
1422 memset(&opt_state, 0, sizeof(opt_state));
1423 opt_state.start_revision.kind = svn_opt_revision_unspecified;
1424 opt_state.end_revision.kind = svn_opt_revision_unspecified;
1426 /* Parse options. */
1427 err = svn_cmdline__getopt_init(&os, argc, argv, pool);
1428 if (err)
1429 return svn_cmdline_handle_exit_error(err, pool, "svnadmin: ");
1431 os->interleave = 1;
1433 while (1)
1435 const char *opt_arg;
1436 const char *utf8_opt_arg;
1438 /* Parse the next option. */
1439 apr_err = apr_getopt_long(os, options_table, &opt_id, &opt_arg);
1440 if (APR_STATUS_IS_EOF(apr_err))
1441 break;
1442 else if (apr_err)
1444 subcommand_help(NULL, NULL, pool);
1445 svn_pool_destroy(pool);
1446 return EXIT_FAILURE;
1449 /* Stash the option code in an array before parsing it. */
1450 APR_ARRAY_PUSH(received_opts, int) = opt_id;
1452 switch (opt_id) {
1453 case 'r':
1455 if (opt_state.start_revision.kind != svn_opt_revision_unspecified)
1457 err = svn_error_create
1458 (SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
1459 _("Multiple revision arguments encountered; "
1460 "try '-r N:M' instead of '-r N -r M'"));
1461 return svn_cmdline_handle_exit_error(err, pool, "svnadmin: ");
1463 if (svn_opt_parse_revision(&(opt_state.start_revision),
1464 &(opt_state.end_revision),
1465 opt_arg, pool) != 0)
1467 err = svn_utf_cstring_to_utf8(&utf8_opt_arg, opt_arg,
1468 pool);
1470 if (! err)
1471 err = svn_error_createf
1472 (SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
1473 _("Syntax error in revision argument '%s'"),
1474 utf8_opt_arg);
1475 return svn_cmdline_handle_exit_error(err, pool, "svnadmin: ");
1478 break;
1479 case 'q':
1480 opt_state.quiet = TRUE;
1481 break;
1482 case 'h':
1483 case '?':
1484 opt_state.help = TRUE;
1485 break;
1486 case svnadmin__version:
1487 opt_state.version = TRUE;
1488 break;
1489 case svnadmin__incremental:
1490 opt_state.incremental = TRUE;
1491 break;
1492 case svnadmin__deltas:
1493 opt_state.use_deltas = TRUE;
1494 break;
1495 case svnadmin__ignore_uuid:
1496 opt_state.uuid_action = svn_repos_load_uuid_ignore;
1497 break;
1498 case svnadmin__force_uuid:
1499 opt_state.uuid_action = svn_repos_load_uuid_force;
1500 break;
1501 case svnadmin__pre_1_4_compatible:
1502 opt_state.pre_1_4_compatible = TRUE;
1503 break;
1504 case svnadmin__pre_1_5_compatible:
1505 opt_state.pre_1_5_compatible = TRUE;
1506 break;
1507 case svnadmin__fs_type:
1508 err = svn_utf_cstring_to_utf8(&opt_state.fs_type, opt_arg, pool);
1509 if (err)
1510 return svn_cmdline_handle_exit_error(err, pool, "svnadmin: ");
1511 break;
1512 case svnadmin__parent_dir:
1513 err = svn_utf_cstring_to_utf8(&opt_state.parent_dir, opt_arg,
1514 pool);
1515 if (err)
1516 return svn_cmdline_handle_exit_error(err, pool, "svnadmin: ");
1517 opt_state.parent_dir
1518 = svn_path_internal_style(opt_state.parent_dir, pool);
1519 break;
1520 case svnadmin__use_pre_commit_hook:
1521 opt_state.use_pre_commit_hook = TRUE;
1522 break;
1523 case svnadmin__use_post_commit_hook:
1524 opt_state.use_post_commit_hook = TRUE;
1525 break;
1526 case svnadmin__use_pre_revprop_change_hook:
1527 opt_state.use_pre_revprop_change_hook = TRUE;
1528 break;
1529 case svnadmin__use_post_revprop_change_hook:
1530 opt_state.use_post_revprop_change_hook = TRUE;
1531 break;
1532 case svnadmin__bdb_txn_nosync:
1533 opt_state.bdb_txn_nosync = TRUE;
1534 break;
1535 case svnadmin__bdb_log_keep:
1536 opt_state.bdb_log_keep = TRUE;
1537 break;
1538 case svnadmin__bypass_hooks:
1539 opt_state.bypass_hooks = TRUE;
1540 break;
1541 case svnadmin__clean_logs:
1542 opt_state.clean_logs = TRUE;
1543 break;
1544 case svnadmin__config_dir:
1545 opt_state.config_dir =
1546 apr_pstrdup(pool, svn_path_canonicalize(opt_arg, pool));
1547 break;
1548 case svnadmin__wait:
1549 opt_state.wait = TRUE;
1550 break;
1551 default:
1553 subcommand_help(NULL, NULL, pool);
1554 svn_pool_destroy(pool);
1555 return EXIT_FAILURE;
1557 } /* close `switch' */
1558 } /* close `while' */
1560 /* If the user asked for help, then the rest of the arguments are
1561 the names of subcommands to get help on (if any), or else they're
1562 just typos/mistakes. Whatever the case, the subcommand to
1563 actually run is subcommand_help(). */
1564 if (opt_state.help)
1565 subcommand = svn_opt_get_canonical_subcommand(cmd_table, "help");
1567 /* If we're not running the `help' subcommand, then look for a
1568 subcommand in the first argument. */
1569 if (subcommand == NULL)
1571 if (os->ind >= os->argc)
1573 if (opt_state.version)
1575 /* Use the "help" subcommand to handle the "--version" option. */
1576 static const svn_opt_subcommand_desc_t pseudo_cmd =
1577 { "--version", subcommand_help, {0}, "",
1578 {svnadmin__version, /* must accept its own option */
1579 } };
1581 subcommand = &pseudo_cmd;
1583 else
1585 svn_error_clear
1586 (svn_cmdline_fprintf(stderr, pool,
1587 _("subcommand argument required\n")));
1588 subcommand_help(NULL, NULL, pool);
1589 svn_pool_destroy(pool);
1590 return EXIT_FAILURE;
1593 else
1595 const char *first_arg = os->argv[os->ind++];
1596 subcommand = svn_opt_get_canonical_subcommand(cmd_table, first_arg);
1597 if (subcommand == NULL)
1599 const char* first_arg_utf8;
1600 err = svn_utf_cstring_to_utf8(&first_arg_utf8, first_arg, pool);
1601 if (err)
1602 return svn_cmdline_handle_exit_error(err, pool, "svnadmin: ");
1603 svn_error_clear
1604 (svn_cmdline_fprintf(stderr, pool,
1605 _("Unknown command: '%s'\n"),
1606 first_arg_utf8));
1607 subcommand_help(NULL, NULL, pool);
1608 svn_pool_destroy(pool);
1609 return EXIT_FAILURE;
1614 /* If there's a second argument, it's probably the repository.
1615 Every subcommand except `help' requires one, so we parse it out
1616 here and store it in opt_state. */
1617 if (subcommand->cmd_func != subcommand_help)
1619 err = parse_local_repos_path(os,
1620 &(opt_state.repository_path),
1621 pool);
1622 if (err)
1623 return svn_cmdline_handle_exit_error(err, pool, "svnadmin: ");
1627 /* If command is hot copy the third argument will be the new
1628 repository path. */
1629 if (subcommand->cmd_func == subcommand_hotcopy)
1631 err = parse_local_repos_path(os,
1632 &(opt_state.new_repository_path),
1633 pool);
1634 if (err)
1635 return svn_cmdline_handle_exit_error(err, pool, "svnadmin: ");
1638 /* Check that the subcommand wasn't passed any inappropriate options. */
1639 for (i = 0; i < received_opts->nelts; i++)
1641 opt_id = APR_ARRAY_IDX(received_opts, i, int);
1643 /* All commands implicitly accept --help, so just skip over this
1644 when we see it. Note that we don't want to include this option
1645 in their "accepted options" list because it would be awfully
1646 redundant to display it in every commands' help text. */
1647 if (opt_id == 'h' || opt_id == '?')
1648 continue;
1650 if (! svn_opt_subcommand_takes_option(subcommand, opt_id))
1652 const char *optstr;
1653 const apr_getopt_option_t *badopt =
1654 svn_opt_get_option_from_code(opt_id, options_table);
1655 svn_opt_format_option(&optstr, badopt, FALSE, pool);
1656 if (subcommand->name[0] == '-')
1657 subcommand_help(NULL, NULL, pool);
1658 else
1659 svn_error_clear
1660 (svn_cmdline_fprintf
1661 (stderr, pool, _("Subcommand '%s' doesn't accept option '%s'\n"
1662 "Type 'svnadmin help %s' for usage.\n"),
1663 subcommand->name, optstr, subcommand->name));
1664 svn_pool_destroy(pool);
1665 return EXIT_FAILURE;
1669 /* Set up our cancellation support. */
1670 setup_cancellation_signals(signal_handler);
1672 #ifdef SIGPIPE
1673 /* Disable SIGPIPE generation for the platforms that have it. */
1674 apr_signal(SIGPIPE, SIG_IGN);
1675 #endif
1677 #ifdef SIGXFSZ
1678 /* Disable SIGXFSZ generation for the platforms that have it, otherwise
1679 * working with large files when compiled against an APR that doesn't have
1680 * large file support will crash the program, which is uncool. */
1681 apr_signal(SIGXFSZ, SIG_IGN);
1682 #endif
1684 /* Run the subcommand. */
1685 err = (*subcommand->cmd_func)(os, &opt_state, pool);
1686 if (err)
1688 /* For argument-related problems, suggest using the 'help'
1689 subcommand. */
1690 if (err->apr_err == SVN_ERR_CL_INSUFFICIENT_ARGS
1691 || err->apr_err == SVN_ERR_CL_ARG_PARSING_ERROR)
1693 err = svn_error_quick_wrap(err,
1694 _("Try 'svnadmin help' for more info"));
1696 return svn_cmdline_handle_exit_error(err, pool, "svnadmin: ");
1698 else
1700 svn_pool_destroy(pool);
1701 /* Ensure that everything is written to stdout, so the user will
1702 see any print errors. */
1703 err = svn_cmdline_fflush(stdout);
1704 if (err)
1706 svn_handle_error2(err, stderr, FALSE, "svnadmin: ");
1707 svn_error_clear(err);
1708 return EXIT_FAILURE;
1710 return EXIT_SUCCESS;
1715 /* This implements `svn_opt_subcommand_t'. */
1716 static svn_error_t *
1717 subcommand_crashtest(apr_getopt_t *os, void *baton, apr_pool_t *pool)
1719 struct svnadmin_opt_state *opt_state = baton;
1720 svn_repos_t *repos;
1722 SVN_ERR(open_repos(&repos, opt_state->repository_path, pool));
1723 abort();
1725 return SVN_NO_ERROR;