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