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