svnadmin.c revision 262253
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_list2(&my_version, checklist, svn_ver_equal); 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 759 760/* Implementation of svn_repos_notify_func_t to wrap the output to a 761 response stream for svn_repos_dump_fs2() and svn_repos_verify_fs() */ 762static void 763repos_notify_handler(void *baton, 764 const svn_repos_notify_t *notify, 765 apr_pool_t *scratch_pool) 766{ 767 svn_stream_t *feedback_stream = baton; 768 apr_size_t len; 769 770 switch (notify->action) 771 { 772 case svn_repos_notify_warning: 773 svn_error_clear(svn_stream_printf(feedback_stream, scratch_pool, 774 "WARNING 0x%04x: %s\n", notify->warning, 775 notify->warning_str)); 776 return; 777 778 case svn_repos_notify_dump_rev_end: 779 svn_error_clear(svn_stream_printf(feedback_stream, scratch_pool, 780 _("* Dumped revision %ld.\n"), 781 notify->revision)); 782 return; 783 784 case svn_repos_notify_verify_rev_end: 785 svn_error_clear(svn_stream_printf(feedback_stream, scratch_pool, 786 _("* Verified revision %ld.\n"), 787 notify->revision)); 788 return; 789 790 case svn_repos_notify_verify_rev_structure: 791 if (notify->revision == SVN_INVALID_REVNUM) 792 svn_error_clear(svn_stream_printf(feedback_stream, scratch_pool, 793 _("* Verifying repository metadata ...\n"))); 794 else 795 svn_error_clear(svn_stream_printf(feedback_stream, scratch_pool, 796 _("* Verifying metadata at revision %ld ...\n"), 797 notify->revision)); 798 return; 799 800 case svn_repos_notify_pack_shard_start: 801 { 802 const char *shardstr = apr_psprintf(scratch_pool, 803 "%" APR_INT64_T_FMT, 804 notify->shard); 805 svn_error_clear(svn_stream_printf(feedback_stream, scratch_pool, 806 _("Packing revisions in shard %s..."), 807 shardstr)); 808 } 809 return; 810 811 case svn_repos_notify_pack_shard_end: 812 svn_error_clear(svn_stream_puts(feedback_stream, _("done.\n"))); 813 return; 814 815 case svn_repos_notify_pack_shard_start_revprop: 816 { 817 const char *shardstr = apr_psprintf(scratch_pool, 818 "%" APR_INT64_T_FMT, 819 notify->shard); 820 svn_error_clear(svn_stream_printf(feedback_stream, scratch_pool, 821 _("Packing revprops in shard %s..."), 822 shardstr)); 823 } 824 return; 825 826 case svn_repos_notify_pack_shard_end_revprop: 827 svn_error_clear(svn_stream_puts(feedback_stream, _("done.\n"))); 828 return; 829 830 case svn_repos_notify_load_txn_committed: 831 if (notify->old_revision == SVN_INVALID_REVNUM) 832 { 833 svn_error_clear(svn_stream_printf(feedback_stream, scratch_pool, 834 _("\n------- Committed revision %ld >>>\n\n"), 835 notify->new_revision)); 836 } 837 else 838 { 839 svn_error_clear(svn_stream_printf(feedback_stream, scratch_pool, 840 _("\n------- Committed new rev %ld" 841 " (loaded from original rev %ld" 842 ") >>>\n\n"), notify->new_revision, 843 notify->old_revision)); 844 } 845 return; 846 847 case svn_repos_notify_load_node_start: 848 { 849 switch (notify->node_action) 850 { 851 case svn_node_action_change: 852 svn_error_clear(svn_stream_printf(feedback_stream, scratch_pool, 853 _(" * editing path : %s ..."), 854 notify->path)); 855 break; 856 857 case svn_node_action_delete: 858 svn_error_clear(svn_stream_printf(feedback_stream, scratch_pool, 859 _(" * deleting path : %s ..."), 860 notify->path)); 861 break; 862 863 case svn_node_action_add: 864 svn_error_clear(svn_stream_printf(feedback_stream, scratch_pool, 865 _(" * adding path : %s ..."), 866 notify->path)); 867 break; 868 869 case svn_node_action_replace: 870 svn_error_clear(svn_stream_printf(feedback_stream, scratch_pool, 871 _(" * replacing path : %s ..."), 872 notify->path)); 873 break; 874 875 } 876 } 877 return; 878 879 case svn_repos_notify_load_node_done: 880 svn_error_clear(svn_stream_printf(feedback_stream, scratch_pool, 881 "%s", _(" done.\n"))); 882 return; 883 884 case svn_repos_notify_load_copied_node: 885 len = 9; 886 svn_error_clear(svn_stream_write(feedback_stream, "COPIED...", &len)); 887 return; 888 889 case svn_repos_notify_load_txn_start: 890 svn_error_clear(svn_stream_printf(feedback_stream, scratch_pool, 891 _("<<< Started new transaction, based on " 892 "original revision %ld\n"), 893 notify->old_revision)); 894 return; 895 896 case svn_repos_notify_load_skipped_rev: 897 svn_error_clear(svn_stream_printf(feedback_stream, scratch_pool, 898 _("<<< Skipped original revision %ld\n"), 899 notify->old_revision)); 900 return; 901 902 case svn_repos_notify_load_normalized_mergeinfo: 903 svn_error_clear(svn_stream_printf(feedback_stream, scratch_pool, 904 _(" removing '\\r' from %s ..."), 905 SVN_PROP_MERGEINFO)); 906 return; 907 908 case svn_repos_notify_mutex_acquired: 909 /* Enable cancellation signal handlers. */ 910 setup_cancellation_signals(signal_handler); 911 return; 912 913 case svn_repos_notify_recover_start: 914 svn_error_clear(svn_stream_printf(feedback_stream, scratch_pool, 915 _("Repository lock acquired.\n" 916 "Please wait; recovering the" 917 " repository may take some time...\n"))); 918 return; 919 920 case svn_repos_notify_upgrade_start: 921 svn_error_clear(svn_stream_puts(feedback_stream, 922 _("Repository lock acquired.\n" 923 "Please wait; upgrading the" 924 " repository may take some time...\n"))); 925 return; 926 927 default: 928 return; 929 } 930} 931 932 933/* Baton for recode_write(). */ 934struct recode_write_baton 935{ 936 apr_pool_t *pool; 937 FILE *out; 938}; 939 940/* This implements the 'svn_write_fn_t' interface. 941 942 Write DATA to ((struct recode_write_baton *) BATON)->out, in the 943 console encoding, using svn_cmdline_fprintf(). DATA is a 944 UTF8-encoded C string, therefore ignore LEN. 945 946 ### This recoding mechanism might want to be abstracted into 947 ### svn_io.h or svn_cmdline.h, if it proves useful elsewhere. */ 948static svn_error_t *recode_write(void *baton, 949 const char *data, 950 apr_size_t *len) 951{ 952 struct recode_write_baton *rwb = baton; 953 svn_pool_clear(rwb->pool); 954 return svn_cmdline_fputs(data, rwb->out, rwb->pool); 955} 956 957/* Create a stream, to write to STD_STREAM, that uses recode_write() 958 to perform UTF-8 to console encoding translation. */ 959static svn_stream_t * 960recode_stream_create(FILE *std_stream, apr_pool_t *pool) 961{ 962 struct recode_write_baton *std_stream_rwb = 963 apr_palloc(pool, sizeof(struct recode_write_baton)); 964 965 svn_stream_t *rw_stream = svn_stream_create(std_stream_rwb, pool); 966 std_stream_rwb->pool = svn_pool_create(pool); 967 std_stream_rwb->out = std_stream; 968 svn_stream_set_write(rw_stream, recode_write); 969 return rw_stream; 970} 971 972 973/* This implements `svn_opt_subcommand_t'. */ 974static svn_error_t * 975subcommand_dump(apr_getopt_t *os, void *baton, apr_pool_t *pool) 976{ 977 struct svnadmin_opt_state *opt_state = baton; 978 svn_repos_t *repos; 979 svn_fs_t *fs; 980 svn_stream_t *stdout_stream; 981 svn_revnum_t lower = SVN_INVALID_REVNUM, upper = SVN_INVALID_REVNUM; 982 svn_revnum_t youngest; 983 svn_stream_t *progress_stream = NULL; 984 985 /* Expect no more arguments. */ 986 SVN_ERR(parse_args(NULL, os, 0, 0, pool)); 987 988 SVN_ERR(open_repos(&repos, opt_state->repository_path, pool)); 989 fs = svn_repos_fs(repos); 990 SVN_ERR(svn_fs_youngest_rev(&youngest, fs, pool)); 991 992 /* Find the revision numbers at which to start and end. */ 993 SVN_ERR(get_revnum(&lower, &opt_state->start_revision, 994 youngest, repos, pool)); 995 SVN_ERR(get_revnum(&upper, &opt_state->end_revision, 996 youngest, repos, pool)); 997 998 /* Fill in implied revisions if necessary. */ 999 if (lower == SVN_INVALID_REVNUM) 1000 { 1001 lower = 0; 1002 upper = youngest; 1003 } 1004 else if (upper == SVN_INVALID_REVNUM) 1005 { 1006 upper = lower; 1007 } 1008 1009 if (lower > upper) 1010 return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL, 1011 _("First revision cannot be higher than second")); 1012 1013 SVN_ERR(svn_stream_for_stdout(&stdout_stream, pool)); 1014 1015 /* Progress feedback goes to STDERR, unless they asked to suppress it. */ 1016 if (! opt_state->quiet) 1017 progress_stream = recode_stream_create(stderr, pool); 1018 1019 SVN_ERR(svn_repos_dump_fs3(repos, stdout_stream, lower, upper, 1020 opt_state->incremental, opt_state->use_deltas, 1021 !opt_state->quiet ? repos_notify_handler : NULL, 1022 progress_stream, check_cancel, NULL, pool)); 1023 1024 return SVN_NO_ERROR; 1025} 1026 1027struct freeze_baton_t { 1028 const char *command; 1029 const char **args; 1030 int status; 1031}; 1032 1033/* Implements svn_repos_freeze_func_t */ 1034static svn_error_t * 1035freeze_body(void *baton, 1036 apr_pool_t *pool) 1037{ 1038 struct freeze_baton_t *b = baton; 1039 apr_status_t apr_err; 1040 apr_file_t *infile, *outfile, *errfile; 1041 1042 apr_err = apr_file_open_stdin(&infile, pool); 1043 if (apr_err) 1044 return svn_error_wrap_apr(apr_err, "Can't open stdin"); 1045 apr_err = apr_file_open_stdout(&outfile, pool); 1046 if (apr_err) 1047 return svn_error_wrap_apr(apr_err, "Can't open stdout"); 1048 apr_err = apr_file_open_stderr(&errfile, pool); 1049 if (apr_err) 1050 return svn_error_wrap_apr(apr_err, "Can't open stderr"); 1051 1052 SVN_ERR(svn_io_run_cmd(NULL, b->command, b->args, &b->status, 1053 NULL, TRUE, 1054 infile, outfile, errfile, pool)); 1055 1056 return SVN_NO_ERROR; 1057} 1058 1059static svn_error_t * 1060subcommand_freeze(apr_getopt_t *os, void *baton, apr_pool_t *pool) 1061{ 1062 struct svnadmin_opt_state *opt_state = baton; 1063 apr_array_header_t *paths; 1064 apr_array_header_t *args; 1065 int i; 1066 struct freeze_baton_t b; 1067 1068 SVN_ERR(svn_opt_parse_all_args(&args, os, pool)); 1069 1070 if (!args->nelts) 1071 return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, 0, 1072 _("No program provided")); 1073 1074 if (!opt_state->filedata) 1075 { 1076 /* One repository on the command line. */ 1077 paths = apr_array_make(pool, 1, sizeof(const char *)); 1078 APR_ARRAY_PUSH(paths, const char *) = opt_state->repository_path; 1079 } 1080 else 1081 { 1082 /* All repositories in filedata. */ 1083 paths = svn_cstring_split(opt_state->filedata->data, "\n", FALSE, pool); 1084 } 1085 1086 b.command = APR_ARRAY_IDX(args, 0, const char *); 1087 b.args = apr_palloc(pool, sizeof(char *) * args->nelts + 1); 1088 for (i = 0; i < args->nelts; ++i) 1089 b.args[i] = APR_ARRAY_IDX(args, i, const char *); 1090 b.args[args->nelts] = NULL; 1091 1092 SVN_ERR(svn_repos_freeze(paths, freeze_body, &b, pool)); 1093 1094 /* Make any non-zero status visible to the user. */ 1095 if (b.status) 1096 exit(b.status); 1097 1098 return SVN_NO_ERROR; 1099} 1100 1101 1102/* This implements `svn_opt_subcommand_t'. */ 1103static svn_error_t * 1104subcommand_help(apr_getopt_t *os, void *baton, apr_pool_t *pool) 1105{ 1106 struct svnadmin_opt_state *opt_state = baton; 1107 const char *header = 1108 _("general usage: svnadmin SUBCOMMAND REPOS_PATH [ARGS & OPTIONS ...]\n" 1109 "Type 'svnadmin help <subcommand>' for help on a specific subcommand.\n" 1110 "Type 'svnadmin --version' to see the program version and FS modules.\n" 1111 "\n" 1112 "Available subcommands:\n"); 1113 1114 const char *fs_desc_start 1115 = _("The following repository back-end (FS) modules are available:\n\n"); 1116 1117 svn_stringbuf_t *version_footer; 1118 1119 version_footer = svn_stringbuf_create(fs_desc_start, pool); 1120 SVN_ERR(svn_fs_print_modules(version_footer, pool)); 1121 1122 SVN_ERR(svn_opt_print_help4(os, "svnadmin", 1123 opt_state ? opt_state->version : FALSE, 1124 opt_state ? opt_state->quiet : FALSE, 1125 /*###opt_state ? opt_state->verbose :*/ FALSE, 1126 version_footer->data, 1127 header, cmd_table, options_table, NULL, NULL, 1128 pool)); 1129 1130 return SVN_NO_ERROR; 1131} 1132 1133 1134/* Set *REVNUM to the revision number of a numeric REV, or to 1135 SVN_INVALID_REVNUM if REV is unspecified. */ 1136static svn_error_t * 1137optrev_to_revnum(svn_revnum_t *revnum, const svn_opt_revision_t *opt_rev) 1138{ 1139 if (opt_rev->kind == svn_opt_revision_number) 1140 { 1141 *revnum = opt_rev->value.number; 1142 if (! SVN_IS_VALID_REVNUM(*revnum)) 1143 return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL, 1144 _("Invalid revision number (%ld) specified"), 1145 *revnum); 1146 } 1147 else if (opt_rev->kind == svn_opt_revision_unspecified) 1148 { 1149 *revnum = SVN_INVALID_REVNUM; 1150 } 1151 else 1152 { 1153 return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL, 1154 _("Non-numeric revision specified")); 1155 } 1156 return SVN_NO_ERROR; 1157} 1158 1159 1160/* This implements `svn_opt_subcommand_t'. */ 1161static svn_error_t * 1162subcommand_load(apr_getopt_t *os, void *baton, apr_pool_t *pool) 1163{ 1164 svn_error_t *err; 1165 struct svnadmin_opt_state *opt_state = baton; 1166 svn_repos_t *repos; 1167 svn_revnum_t lower = SVN_INVALID_REVNUM, upper = SVN_INVALID_REVNUM; 1168 svn_stream_t *stdin_stream, *stdout_stream = NULL; 1169 1170 /* Expect no more arguments. */ 1171 SVN_ERR(parse_args(NULL, os, 0, 0, pool)); 1172 1173 /* Find the revision numbers at which to start and end. We only 1174 support a limited set of revision kinds: number and unspecified. */ 1175 SVN_ERR(optrev_to_revnum(&lower, &opt_state->start_revision)); 1176 SVN_ERR(optrev_to_revnum(&upper, &opt_state->end_revision)); 1177 1178 /* Fill in implied revisions if necessary. */ 1179 if ((upper == SVN_INVALID_REVNUM) && (lower != SVN_INVALID_REVNUM)) 1180 { 1181 upper = lower; 1182 } 1183 else if ((upper != SVN_INVALID_REVNUM) && (lower == SVN_INVALID_REVNUM)) 1184 { 1185 lower = upper; 1186 } 1187 1188 /* Ensure correct range ordering. */ 1189 if (lower > upper) 1190 { 1191 return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL, 1192 _("First revision cannot be higher than second")); 1193 } 1194 1195 SVN_ERR(open_repos(&repos, opt_state->repository_path, pool)); 1196 1197 /* Read the stream from STDIN. Users can redirect a file. */ 1198 SVN_ERR(svn_stream_for_stdin(&stdin_stream, pool)); 1199 1200 /* Progress feedback goes to STDOUT, unless they asked to suppress it. */ 1201 if (! opt_state->quiet) 1202 stdout_stream = recode_stream_create(stdout, pool); 1203 1204 err = svn_repos_load_fs4(repos, stdin_stream, lower, upper, 1205 opt_state->uuid_action, opt_state->parent_dir, 1206 opt_state->use_pre_commit_hook, 1207 opt_state->use_post_commit_hook, 1208 !opt_state->bypass_prop_validation, 1209 opt_state->quiet ? NULL : repos_notify_handler, 1210 stdout_stream, check_cancel, NULL, pool); 1211 if (err && err->apr_err == SVN_ERR_BAD_PROPERTY_VALUE) 1212 return svn_error_quick_wrap(err, 1213 _("Invalid property value found in " 1214 "dumpstream; consider repairing the source " 1215 "or using --bypass-prop-validation while " 1216 "loading.")); 1217 return err; 1218} 1219 1220 1221/* This implements `svn_opt_subcommand_t'. */ 1222static svn_error_t * 1223subcommand_lstxns(apr_getopt_t *os, void *baton, apr_pool_t *pool) 1224{ 1225 struct svnadmin_opt_state *opt_state = baton; 1226 svn_repos_t *repos; 1227 svn_fs_t *fs; 1228 apr_array_header_t *txns; 1229 int i; 1230 1231 /* Expect no more arguments. */ 1232 SVN_ERR(parse_args(NULL, os, 0, 0, pool)); 1233 1234 SVN_ERR(open_repos(&repos, opt_state->repository_path, pool)); 1235 fs = svn_repos_fs(repos); 1236 SVN_ERR(svn_fs_list_transactions(&txns, fs, pool)); 1237 1238 /* Loop, printing revisions. */ 1239 for (i = 0; i < txns->nelts; i++) 1240 { 1241 SVN_ERR(svn_cmdline_printf(pool, "%s\n", 1242 APR_ARRAY_IDX(txns, i, const char *))); 1243 } 1244 1245 return SVN_NO_ERROR; 1246} 1247 1248 1249/* This implements `svn_opt_subcommand_t'. */ 1250static svn_error_t * 1251subcommand_recover(apr_getopt_t *os, void *baton, apr_pool_t *pool) 1252{ 1253 svn_revnum_t youngest_rev; 1254 svn_repos_t *repos; 1255 svn_error_t *err; 1256 struct svnadmin_opt_state *opt_state = baton; 1257 svn_stream_t *stdout_stream; 1258 1259 /* Expect no more arguments. */ 1260 SVN_ERR(parse_args(NULL, os, 0, 0, pool)); 1261 1262 SVN_ERR(svn_stream_for_stdout(&stdout_stream, pool)); 1263 1264 /* Restore default signal handlers until after we have acquired the 1265 * exclusive lock so that the user interrupt before we actually 1266 * touch the repository. */ 1267 setup_cancellation_signals(SIG_DFL); 1268 1269 err = svn_repos_recover4(opt_state->repository_path, TRUE, 1270 repos_notify_handler, stdout_stream, 1271 check_cancel, NULL, pool); 1272 if (err) 1273 { 1274 if (! APR_STATUS_IS_EAGAIN(err->apr_err)) 1275 return err; 1276 svn_error_clear(err); 1277 if (! opt_state->wait) 1278 return svn_error_create(SVN_ERR_REPOS_LOCKED, NULL, 1279 _("Failed to get exclusive repository " 1280 "access; perhaps another process\n" 1281 "such as httpd, svnserve or svn " 1282 "has it open?")); 1283 SVN_ERR(svn_cmdline_printf(pool, 1284 _("Waiting on repository lock; perhaps" 1285 " another process has it open?\n"))); 1286 SVN_ERR(svn_cmdline_fflush(stdout)); 1287 SVN_ERR(svn_repos_recover4(opt_state->repository_path, FALSE, 1288 repos_notify_handler, stdout_stream, 1289 check_cancel, NULL, pool)); 1290 } 1291 1292 SVN_ERR(svn_cmdline_printf(pool, _("\nRecovery completed.\n"))); 1293 1294 /* Since db transactions may have been replayed, it's nice to tell 1295 people what the latest revision is. It also proves that the 1296 recovery actually worked. */ 1297 SVN_ERR(open_repos(&repos, opt_state->repository_path, pool)); 1298 SVN_ERR(svn_fs_youngest_rev(&youngest_rev, svn_repos_fs(repos), pool)); 1299 SVN_ERR(svn_cmdline_printf(pool, _("The latest repos revision is %ld.\n"), 1300 youngest_rev)); 1301 1302 return SVN_NO_ERROR; 1303} 1304 1305 1306/* This implements `svn_opt_subcommand_t'. */ 1307static svn_error_t * 1308list_dblogs(apr_getopt_t *os, void *baton, svn_boolean_t only_unused, 1309 apr_pool_t *pool) 1310{ 1311 struct svnadmin_opt_state *opt_state = baton; 1312 apr_array_header_t *logfiles; 1313 int i; 1314 1315 /* Expect no more arguments. */ 1316 SVN_ERR(parse_args(NULL, os, 0, 0, pool)); 1317 1318 SVN_ERR(svn_repos_db_logfiles(&logfiles, 1319 opt_state->repository_path, 1320 only_unused, 1321 pool)); 1322 1323 /* Loop, printing log files. We append the log paths to the 1324 repository path, making sure to return everything to the native 1325 style before printing. */ 1326 for (i = 0; i < logfiles->nelts; i++) 1327 { 1328 const char *log_utf8; 1329 log_utf8 = svn_dirent_join(opt_state->repository_path, 1330 APR_ARRAY_IDX(logfiles, i, const char *), 1331 pool); 1332 log_utf8 = svn_dirent_local_style(log_utf8, pool); 1333 SVN_ERR(svn_cmdline_printf(pool, "%s\n", log_utf8)); 1334 } 1335 1336 return SVN_NO_ERROR; 1337} 1338 1339 1340/* This implements `svn_opt_subcommand_t'. */ 1341static svn_error_t * 1342subcommand_list_dblogs(apr_getopt_t *os, void *baton, apr_pool_t *pool) 1343{ 1344 SVN_ERR(list_dblogs(os, baton, FALSE, pool)); 1345 return SVN_NO_ERROR; 1346} 1347 1348 1349/* This implements `svn_opt_subcommand_t'. */ 1350static svn_error_t * 1351subcommand_list_unused_dblogs(apr_getopt_t *os, void *baton, apr_pool_t *pool) 1352{ 1353 /* Expect no more arguments. */ 1354 SVN_ERR(parse_args(NULL, os, 0, 0, pool)); 1355 1356 SVN_ERR(list_dblogs(os, baton, TRUE, pool)); 1357 return SVN_NO_ERROR; 1358} 1359 1360 1361/* This implements `svn_opt_subcommand_t'. */ 1362static svn_error_t * 1363subcommand_rmtxns(apr_getopt_t *os, void *baton, apr_pool_t *pool) 1364{ 1365 struct svnadmin_opt_state *opt_state = baton; 1366 svn_repos_t *repos; 1367 svn_fs_t *fs; 1368 svn_fs_txn_t *txn; 1369 apr_array_header_t *args; 1370 int i; 1371 apr_pool_t *subpool = svn_pool_create(pool); 1372 1373 SVN_ERR(svn_opt_parse_all_args(&args, os, pool)); 1374 1375 SVN_ERR(open_repos(&repos, opt_state->repository_path, pool)); 1376 fs = svn_repos_fs(repos); 1377 1378 /* All the rest of the arguments are transaction names. */ 1379 for (i = 0; i < args->nelts; i++) 1380 { 1381 const char *txn_name = APR_ARRAY_IDX(args, i, const char *); 1382 const char *txn_name_utf8; 1383 svn_error_t *err; 1384 1385 svn_pool_clear(subpool); 1386 1387 SVN_ERR(svn_utf_cstring_to_utf8(&txn_name_utf8, txn_name, subpool)); 1388 1389 /* Try to open the txn. If that succeeds, try to abort it. */ 1390 err = svn_fs_open_txn(&txn, fs, txn_name_utf8, subpool); 1391 if (! err) 1392 err = svn_fs_abort_txn(txn, subpool); 1393 1394 /* If either the open or the abort of the txn fails because that 1395 transaction is dead, just try to purge the thing. Else, 1396 there was either an error worth reporting, or not error at 1397 all. */ 1398 if (err && (err->apr_err == SVN_ERR_FS_TRANSACTION_DEAD)) 1399 { 1400 svn_error_clear(err); 1401 err = svn_fs_purge_txn(fs, txn_name_utf8, subpool); 1402 } 1403 1404 /* If we had a real from the txn open, abort, or purge, we clear 1405 that error and just report to the user that we had an issue 1406 with this particular txn. */ 1407 if (err) 1408 { 1409 svn_handle_error2(err, stderr, FALSE /* non-fatal */, "svnadmin: "); 1410 svn_error_clear(err); 1411 } 1412 else if (! opt_state->quiet) 1413 { 1414 SVN_ERR(svn_cmdline_printf(subpool, _("Transaction '%s' removed.\n"), 1415 txn_name)); 1416 } 1417 } 1418 1419 svn_pool_destroy(subpool); 1420 1421 return SVN_NO_ERROR; 1422} 1423 1424 1425/* A helper for the 'setrevprop' and 'setlog' commands. Expects 1426 OPT_STATE->use_pre_revprop_change_hook and 1427 OPT_STATE->use_post_revprop_change_hook to be set appropriately. */ 1428static svn_error_t * 1429set_revprop(const char *prop_name, const char *filename, 1430 struct svnadmin_opt_state *opt_state, apr_pool_t *pool) 1431{ 1432 svn_repos_t *repos; 1433 svn_string_t *prop_value = svn_string_create_empty(pool); 1434 svn_stringbuf_t *file_contents; 1435 1436 SVN_ERR(svn_stringbuf_from_file2(&file_contents, filename, pool)); 1437 1438 prop_value->data = file_contents->data; 1439 prop_value->len = file_contents->len; 1440 1441 SVN_ERR(svn_subst_translate_string2(&prop_value, NULL, NULL, prop_value, 1442 NULL, FALSE, pool, pool)); 1443 1444 /* Open the filesystem */ 1445 SVN_ERR(open_repos(&repos, opt_state->repository_path, pool)); 1446 1447 /* If we are bypassing the hooks system, we just hit the filesystem 1448 directly. */ 1449 SVN_ERR(svn_repos_fs_change_rev_prop4( 1450 repos, opt_state->start_revision.value.number, 1451 NULL, prop_name, NULL, prop_value, 1452 opt_state->use_pre_revprop_change_hook, 1453 opt_state->use_post_revprop_change_hook, 1454 NULL, NULL, pool)); 1455 1456 return SVN_NO_ERROR; 1457} 1458 1459 1460/* This implements `svn_opt_subcommand_t'. */ 1461static svn_error_t * 1462subcommand_setrevprop(apr_getopt_t *os, void *baton, apr_pool_t *pool) 1463{ 1464 struct svnadmin_opt_state *opt_state = baton; 1465 apr_array_header_t *args; 1466 const char *prop_name, *filename; 1467 1468 /* Expect two more arguments: NAME FILE */ 1469 SVN_ERR(parse_args(&args, os, 2, 2, pool)); 1470 prop_name = APR_ARRAY_IDX(args, 0, const char *); 1471 filename = APR_ARRAY_IDX(args, 1, const char *); 1472 SVN_ERR(target_arg_to_dirent(&filename, filename, pool)); 1473 1474 if (opt_state->start_revision.kind != svn_opt_revision_number) 1475 return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL, 1476 _("Missing revision")); 1477 else if (opt_state->end_revision.kind != svn_opt_revision_unspecified) 1478 return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL, 1479 _("Only one revision allowed")); 1480 1481 return set_revprop(prop_name, filename, opt_state, pool); 1482} 1483 1484 1485/* This implements `svn_opt_subcommand_t'. */ 1486static svn_error_t * 1487subcommand_setuuid(apr_getopt_t *os, void *baton, apr_pool_t *pool) 1488{ 1489 struct svnadmin_opt_state *opt_state = baton; 1490 apr_array_header_t *args; 1491 svn_repos_t *repos; 1492 svn_fs_t *fs; 1493 const char *uuid = NULL; 1494 1495 /* Expect zero or one more arguments: [UUID] */ 1496 SVN_ERR(parse_args(&args, os, 0, 1, pool)); 1497 if (args->nelts == 1) 1498 uuid = APR_ARRAY_IDX(args, 0, const char *); 1499 1500 SVN_ERR(open_repos(&repos, opt_state->repository_path, pool)); 1501 fs = svn_repos_fs(repos); 1502 return svn_fs_set_uuid(fs, uuid, pool); 1503} 1504 1505 1506/* This implements `svn_opt_subcommand_t'. */ 1507static svn_error_t * 1508subcommand_setlog(apr_getopt_t *os, void *baton, apr_pool_t *pool) 1509{ 1510 struct svnadmin_opt_state *opt_state = baton; 1511 apr_array_header_t *args; 1512 const char *filename; 1513 1514 /* Expect one more argument: FILE */ 1515 SVN_ERR(parse_args(&args, os, 1, 1, pool)); 1516 filename = APR_ARRAY_IDX(args, 0, const char *); 1517 SVN_ERR(target_arg_to_dirent(&filename, filename, pool)); 1518 1519 if (opt_state->start_revision.kind != svn_opt_revision_number) 1520 return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL, 1521 _("Missing revision")); 1522 else if (opt_state->end_revision.kind != svn_opt_revision_unspecified) 1523 return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL, 1524 _("Only one revision allowed")); 1525 1526 /* set_revprop() responds only to pre-/post-revprop-change opts. */ 1527 if (!opt_state->bypass_hooks) 1528 { 1529 opt_state->use_pre_revprop_change_hook = TRUE; 1530 opt_state->use_post_revprop_change_hook = TRUE; 1531 } 1532 1533 return set_revprop(SVN_PROP_REVISION_LOG, filename, opt_state, pool); 1534} 1535 1536 1537/* This implements 'svn_opt_subcommand_t'. */ 1538static svn_error_t * 1539subcommand_pack(apr_getopt_t *os, void *baton, apr_pool_t *pool) 1540{ 1541 struct svnadmin_opt_state *opt_state = baton; 1542 svn_repos_t *repos; 1543 svn_stream_t *progress_stream = NULL; 1544 1545 /* Expect no more arguments. */ 1546 SVN_ERR(parse_args(NULL, os, 0, 0, pool)); 1547 1548 SVN_ERR(open_repos(&repos, opt_state->repository_path, pool)); 1549 1550 /* Progress feedback goes to STDOUT, unless they asked to suppress it. */ 1551 if (! opt_state->quiet) 1552 progress_stream = recode_stream_create(stdout, pool); 1553 1554 return svn_error_trace( 1555 svn_repos_fs_pack2(repos, !opt_state->quiet ? repos_notify_handler : NULL, 1556 progress_stream, check_cancel, NULL, pool)); 1557} 1558 1559 1560/* This implements `svn_opt_subcommand_t'. */ 1561static svn_error_t * 1562subcommand_verify(apr_getopt_t *os, void *baton, apr_pool_t *pool) 1563{ 1564 struct svnadmin_opt_state *opt_state = baton; 1565 svn_repos_t *repos; 1566 svn_fs_t *fs; 1567 svn_revnum_t youngest, lower, upper; 1568 svn_stream_t *progress_stream = NULL; 1569 1570 /* Expect no more arguments. */ 1571 SVN_ERR(parse_args(NULL, os, 0, 0, pool)); 1572 1573 if (opt_state->txn_id 1574 && (opt_state->start_revision.kind != svn_opt_revision_unspecified 1575 || opt_state->end_revision.kind != svn_opt_revision_unspecified)) 1576 { 1577 return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL, 1578 _("--revision (-r) and --transaction (-t) " 1579 "are mutually exclusive")); 1580 } 1581 1582 SVN_ERR(open_repos(&repos, opt_state->repository_path, pool)); 1583 fs = svn_repos_fs(repos); 1584 SVN_ERR(svn_fs_youngest_rev(&youngest, fs, pool)); 1585 1586 /* Usage 2. */ 1587 if (opt_state->txn_id) 1588 { 1589 svn_fs_txn_t *txn; 1590 svn_fs_root_t *root; 1591 1592 SVN_ERR(svn_fs_open_txn(&txn, fs, opt_state->txn_id, pool)); 1593 SVN_ERR(svn_fs_txn_root(&root, txn, pool)); 1594 SVN_ERR(svn_fs_verify_root(root, pool)); 1595 return SVN_NO_ERROR; 1596 } 1597 else 1598 /* Usage 1. */ 1599 ; 1600 1601 /* Find the revision numbers at which to start and end. */ 1602 SVN_ERR(get_revnum(&lower, &opt_state->start_revision, 1603 youngest, repos, pool)); 1604 SVN_ERR(get_revnum(&upper, &opt_state->end_revision, 1605 youngest, repos, pool)); 1606 1607 if (upper == SVN_INVALID_REVNUM) 1608 { 1609 upper = lower; 1610 } 1611 1612 if (! opt_state->quiet) 1613 progress_stream = recode_stream_create(stderr, pool); 1614 1615 return svn_repos_verify_fs2(repos, lower, upper, 1616 !opt_state->quiet 1617 ? repos_notify_handler : NULL, 1618 progress_stream, check_cancel, NULL, pool); 1619} 1620 1621/* This implements `svn_opt_subcommand_t'. */ 1622svn_error_t * 1623subcommand_hotcopy(apr_getopt_t *os, void *baton, apr_pool_t *pool) 1624{ 1625 struct svnadmin_opt_state *opt_state = baton; 1626 apr_array_header_t *targets; 1627 const char *new_repos_path; 1628 1629 /* Expect one more argument: NEW_REPOS_PATH */ 1630 SVN_ERR(parse_args(&targets, os, 1, 1, pool)); 1631 new_repos_path = APR_ARRAY_IDX(targets, 0, const char *); 1632 SVN_ERR(target_arg_to_dirent(&new_repos_path, new_repos_path, pool)); 1633 1634 return svn_repos_hotcopy2(opt_state->repository_path, new_repos_path, 1635 opt_state->clean_logs, opt_state->incremental, 1636 check_cancel, NULL, pool); 1637} 1638 1639/* This implements `svn_opt_subcommand_t'. */ 1640static svn_error_t * 1641subcommand_lock(apr_getopt_t *os, void *baton, apr_pool_t *pool) 1642{ 1643 struct svnadmin_opt_state *opt_state = baton; 1644 svn_repos_t *repos; 1645 svn_fs_t *fs; 1646 svn_fs_access_t *access; 1647 apr_array_header_t *args; 1648 const char *username; 1649 const char *lock_path; 1650 const char *comment_file_name; 1651 svn_stringbuf_t *file_contents; 1652 const char *lock_path_utf8; 1653 svn_lock_t *lock; 1654 const char *lock_token = NULL; 1655 1656 /* Expect three more arguments: PATH USERNAME COMMENT-FILE */ 1657 SVN_ERR(parse_args(&args, os, 3, 4, pool)); 1658 lock_path = APR_ARRAY_IDX(args, 0, const char *); 1659 username = APR_ARRAY_IDX(args, 1, const char *); 1660 comment_file_name = APR_ARRAY_IDX(args, 2, const char *); 1661 1662 /* Expect one more optional argument: TOKEN */ 1663 if (args->nelts == 4) 1664 lock_token = APR_ARRAY_IDX(args, 3, const char *); 1665 1666 SVN_ERR(target_arg_to_dirent(&comment_file_name, comment_file_name, pool)); 1667 1668 SVN_ERR(open_repos(&repos, opt_state->repository_path, pool)); 1669 fs = svn_repos_fs(repos); 1670 1671 /* Create an access context describing the user. */ 1672 SVN_ERR(svn_fs_create_access(&access, username, pool)); 1673 1674 /* Attach the access context to the filesystem. */ 1675 SVN_ERR(svn_fs_set_access(fs, access)); 1676 1677 SVN_ERR(svn_stringbuf_from_file2(&file_contents, comment_file_name, pool)); 1678 1679 SVN_ERR(svn_utf_cstring_to_utf8(&lock_path_utf8, lock_path, pool)); 1680 1681 if (opt_state->bypass_hooks) 1682 SVN_ERR(svn_fs_lock(&lock, fs, lock_path_utf8, 1683 lock_token, 1684 file_contents->data, /* comment */ 1685 0, /* is_dav_comment */ 1686 0, /* no expiration time. */ 1687 SVN_INVALID_REVNUM, 1688 FALSE, pool)); 1689 else 1690 SVN_ERR(svn_repos_fs_lock(&lock, repos, lock_path_utf8, 1691 lock_token, 1692 file_contents->data, /* comment */ 1693 0, /* is_dav_comment */ 1694 0, /* no expiration time. */ 1695 SVN_INVALID_REVNUM, 1696 FALSE, pool)); 1697 1698 SVN_ERR(svn_cmdline_printf(pool, _("'%s' locked by user '%s'.\n"), 1699 lock_path, username)); 1700 return SVN_NO_ERROR; 1701} 1702 1703static svn_error_t * 1704subcommand_lslocks(apr_getopt_t *os, void *baton, apr_pool_t *pool) 1705{ 1706 struct svnadmin_opt_state *opt_state = baton; 1707 apr_array_header_t *targets; 1708 svn_repos_t *repos; 1709 const char *fs_path = "/"; 1710 apr_hash_t *locks; 1711 apr_hash_index_t *hi; 1712 1713 SVN_ERR(svn_opt__args_to_target_array(&targets, os, 1714 apr_array_make(pool, 0, 1715 sizeof(const char *)), 1716 pool)); 1717 if (targets->nelts > 1) 1718 return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, 0, 1719 _("Too many arguments given")); 1720 if (targets->nelts) 1721 fs_path = APR_ARRAY_IDX(targets, 0, const char *); 1722 1723 SVN_ERR(open_repos(&repos, opt_state->repository_path, pool)); 1724 1725 /* Fetch all locks on or below the root directory. */ 1726 SVN_ERR(svn_repos_fs_get_locks2(&locks, repos, fs_path, svn_depth_infinity, 1727 NULL, NULL, pool)); 1728 1729 for (hi = apr_hash_first(pool, locks); hi; hi = apr_hash_next(hi)) 1730 { 1731 const char *cr_date, *exp_date = ""; 1732 const char *path = svn__apr_hash_index_key(hi); 1733 svn_lock_t *lock = svn__apr_hash_index_val(hi); 1734 int comment_lines = 0; 1735 1736 cr_date = svn_time_to_human_cstring(lock->creation_date, pool); 1737 1738 if (lock->expiration_date) 1739 exp_date = svn_time_to_human_cstring(lock->expiration_date, pool); 1740 1741 if (lock->comment) 1742 comment_lines = svn_cstring_count_newlines(lock->comment) + 1; 1743 1744 SVN_ERR(svn_cmdline_printf(pool, _("Path: %s\n"), path)); 1745 SVN_ERR(svn_cmdline_printf(pool, _("UUID Token: %s\n"), lock->token)); 1746 SVN_ERR(svn_cmdline_printf(pool, _("Owner: %s\n"), lock->owner)); 1747 SVN_ERR(svn_cmdline_printf(pool, _("Created: %s\n"), cr_date)); 1748 SVN_ERR(svn_cmdline_printf(pool, _("Expires: %s\n"), exp_date)); 1749 SVN_ERR(svn_cmdline_printf(pool, 1750 Q_("Comment (%i line):\n%s\n\n", 1751 "Comment (%i lines):\n%s\n\n", 1752 comment_lines), 1753 comment_lines, 1754 lock->comment ? lock->comment : "")); 1755 } 1756 1757 return SVN_NO_ERROR; 1758} 1759 1760 1761 1762static svn_error_t * 1763subcommand_rmlocks(apr_getopt_t *os, void *baton, apr_pool_t *pool) 1764{ 1765 struct svnadmin_opt_state *opt_state = baton; 1766 svn_repos_t *repos; 1767 svn_fs_t *fs; 1768 svn_fs_access_t *access; 1769 svn_error_t *err; 1770 apr_array_header_t *args; 1771 int i; 1772 const char *username; 1773 apr_pool_t *subpool = svn_pool_create(pool); 1774 1775 SVN_ERR(open_repos(&repos, opt_state->repository_path, pool)); 1776 fs = svn_repos_fs(repos); 1777 1778 /* svn_fs_unlock() demands that some username be associated with the 1779 filesystem, so just use the UID of the person running 'svnadmin'.*/ 1780 username = svn_user_get_name(pool); 1781 if (! username) 1782 username = "administrator"; 1783 1784 /* Create an access context describing the current user. */ 1785 SVN_ERR(svn_fs_create_access(&access, username, pool)); 1786 1787 /* Attach the access context to the filesystem. */ 1788 SVN_ERR(svn_fs_set_access(fs, access)); 1789 1790 /* Parse out any options. */ 1791 SVN_ERR(svn_opt_parse_all_args(&args, os, pool)); 1792 1793 /* Our usage requires at least one FS path. */ 1794 if (args->nelts == 0) 1795 return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, 0, 1796 _("No paths to unlock provided")); 1797 1798 /* All the rest of the arguments are paths from which to remove locks. */ 1799 for (i = 0; i < args->nelts; i++) 1800 { 1801 const char *lock_path = APR_ARRAY_IDX(args, i, const char *); 1802 const char *lock_path_utf8; 1803 svn_lock_t *lock; 1804 1805 SVN_ERR(svn_utf_cstring_to_utf8(&lock_path_utf8, lock_path, subpool)); 1806 1807 /* Fetch the path's svn_lock_t. */ 1808 err = svn_fs_get_lock(&lock, fs, lock_path_utf8, subpool); 1809 if (err) 1810 goto move_on; 1811 if (! lock) 1812 { 1813 SVN_ERR(svn_cmdline_printf(subpool, 1814 _("Path '%s' isn't locked.\n"), 1815 lock_path)); 1816 continue; 1817 } 1818 1819 /* Now forcibly destroy the lock. */ 1820 err = svn_fs_unlock(fs, lock_path_utf8, 1821 lock->token, 1 /* force */, subpool); 1822 if (err) 1823 goto move_on; 1824 1825 SVN_ERR(svn_cmdline_printf(subpool, 1826 _("Removed lock on '%s'.\n"), lock->path)); 1827 1828 move_on: 1829 if (err) 1830 { 1831 /* Print the error, but move on to the next lock. */ 1832 svn_handle_error2(err, stderr, FALSE /* non-fatal */, "svnadmin: "); 1833 svn_error_clear(err); 1834 } 1835 1836 svn_pool_clear(subpool); 1837 } 1838 1839 svn_pool_destroy(subpool); 1840 return SVN_NO_ERROR; 1841} 1842 1843 1844/* This implements `svn_opt_subcommand_t'. */ 1845static svn_error_t * 1846subcommand_unlock(apr_getopt_t *os, void *baton, apr_pool_t *pool) 1847{ 1848 struct svnadmin_opt_state *opt_state = baton; 1849 svn_repos_t *repos; 1850 svn_fs_t *fs; 1851 svn_fs_access_t *access; 1852 apr_array_header_t *args; 1853 const char *username; 1854 const char *lock_path; 1855 const char *lock_path_utf8; 1856 const char *lock_token = NULL; 1857 1858 /* Expect three more arguments: PATH USERNAME TOKEN */ 1859 SVN_ERR(parse_args(&args, os, 3, 3, pool)); 1860 lock_path = APR_ARRAY_IDX(args, 0, const char *); 1861 username = APR_ARRAY_IDX(args, 1, const char *); 1862 lock_token = APR_ARRAY_IDX(args, 2, const char *); 1863 1864 /* Open the repos/FS, and associate an access context containing 1865 USERNAME. */ 1866 SVN_ERR(open_repos(&repos, opt_state->repository_path, pool)); 1867 fs = svn_repos_fs(repos); 1868 SVN_ERR(svn_fs_create_access(&access, username, pool)); 1869 SVN_ERR(svn_fs_set_access(fs, access)); 1870 1871 SVN_ERR(svn_utf_cstring_to_utf8(&lock_path_utf8, lock_path, pool)); 1872 if (opt_state->bypass_hooks) 1873 SVN_ERR(svn_fs_unlock(fs, lock_path_utf8, lock_token, 1874 FALSE, pool)); 1875 else 1876 SVN_ERR(svn_repos_fs_unlock(repos, lock_path_utf8, lock_token, 1877 FALSE, pool)); 1878 1879 SVN_ERR(svn_cmdline_printf(pool, _("'%s' unlocked by user '%s'.\n"), 1880 lock_path, username)); 1881 return SVN_NO_ERROR; 1882} 1883 1884 1885/* This implements `svn_opt_subcommand_t'. */ 1886static svn_error_t * 1887subcommand_upgrade(apr_getopt_t *os, void *baton, apr_pool_t *pool) 1888{ 1889 svn_error_t *err; 1890 struct svnadmin_opt_state *opt_state = baton; 1891 svn_stream_t *stdout_stream; 1892 1893 /* Expect no more arguments. */ 1894 SVN_ERR(parse_args(NULL, os, 0, 0, pool)); 1895 1896 SVN_ERR(svn_stream_for_stdout(&stdout_stream, pool)); 1897 1898 /* Restore default signal handlers. */ 1899 setup_cancellation_signals(SIG_DFL); 1900 1901 err = svn_repos_upgrade2(opt_state->repository_path, TRUE, 1902 repos_notify_handler, stdout_stream, pool); 1903 if (err) 1904 { 1905 if (APR_STATUS_IS_EAGAIN(err->apr_err)) 1906 { 1907 svn_error_clear(err); 1908 err = SVN_NO_ERROR; 1909 if (! opt_state->wait) 1910 return svn_error_create(SVN_ERR_REPOS_LOCKED, NULL, 1911 _("Failed to get exclusive repository " 1912 "access; perhaps another process\n" 1913 "such as httpd, svnserve or svn " 1914 "has it open?")); 1915 SVN_ERR(svn_cmdline_printf(pool, 1916 _("Waiting on repository lock; perhaps" 1917 " another process has it open?\n"))); 1918 SVN_ERR(svn_cmdline_fflush(stdout)); 1919 SVN_ERR(svn_repos_upgrade2(opt_state->repository_path, FALSE, 1920 repos_notify_handler, stdout_stream, 1921 pool)); 1922 } 1923 else if (err->apr_err == SVN_ERR_FS_UNSUPPORTED_UPGRADE) 1924 { 1925 return svn_error_quick_wrap(err, 1926 _("Upgrade of this repository's underlying versioned " 1927 "filesystem is not supported; consider " 1928 "dumping and loading the data elsewhere")); 1929 } 1930 else if (err->apr_err == SVN_ERR_REPOS_UNSUPPORTED_UPGRADE) 1931 { 1932 return svn_error_quick_wrap(err, 1933 _("Upgrade of this repository is not supported; consider " 1934 "dumping and loading the data elsewhere")); 1935 } 1936 } 1937 SVN_ERR(err); 1938 1939 SVN_ERR(svn_cmdline_printf(pool, _("\nUpgrade completed.\n"))); 1940 return SVN_NO_ERROR; 1941} 1942 1943 1944 1945/** Main. **/ 1946 1947/* Report and clear the error ERR, and return EXIT_FAILURE. */ 1948#define EXIT_ERROR(err) \ 1949 svn_cmdline_handle_exit_error(err, NULL, "svnadmin: ") 1950 1951/* A redefinition of the public SVN_INT_ERR macro, that suppresses the 1952 * error message if it is SVN_ERR_IO_PIPE_WRITE_ERROR, amd with the 1953 * program name 'svnadmin' instead of 'svn'. */ 1954#undef SVN_INT_ERR 1955#define SVN_INT_ERR(expr) \ 1956 do { \ 1957 svn_error_t *svn_err__temp = (expr); \ 1958 if (svn_err__temp) \ 1959 return EXIT_ERROR(svn_err__temp); \ 1960 } while (0) 1961 1962static int 1963sub_main(int argc, const char *argv[], apr_pool_t *pool) 1964{ 1965 svn_error_t *err; 1966 apr_status_t apr_err; 1967 1968 const svn_opt_subcommand_desc2_t *subcommand = NULL; 1969 struct svnadmin_opt_state opt_state = { 0 }; 1970 apr_getopt_t *os; 1971 int opt_id; 1972 apr_array_header_t *received_opts; 1973 int i; 1974 svn_boolean_t dash_F_arg = FALSE; 1975 1976 received_opts = apr_array_make(pool, SVN_OPT_MAX_OPTIONS, sizeof(int)); 1977 1978 /* Check library versions */ 1979 SVN_INT_ERR(check_lib_versions()); 1980 1981 /* Initialize the FS library. */ 1982 SVN_INT_ERR(svn_fs_initialize(pool)); 1983 1984 if (argc <= 1) 1985 { 1986 SVN_INT_ERR(subcommand_help(NULL, NULL, pool)); 1987 return EXIT_FAILURE; 1988 } 1989 1990 /* Initialize opt_state. */ 1991 opt_state.start_revision.kind = svn_opt_revision_unspecified; 1992 opt_state.end_revision.kind = svn_opt_revision_unspecified; 1993 opt_state.memory_cache_size = svn_cache_config_get()->cache_size; 1994 1995 /* Parse options. */ 1996 SVN_INT_ERR(svn_cmdline__getopt_init(&os, argc, argv, pool)); 1997 1998 os->interleave = 1; 1999 2000 while (1) 2001 { 2002 const char *opt_arg; 2003 const char *utf8_opt_arg; 2004 2005 /* Parse the next option. */ 2006 apr_err = apr_getopt_long(os, options_table, &opt_id, &opt_arg); 2007 if (APR_STATUS_IS_EOF(apr_err)) 2008 break; 2009 else if (apr_err) 2010 { 2011 SVN_INT_ERR(subcommand_help(NULL, NULL, pool)); 2012 return EXIT_FAILURE; 2013 } 2014 2015 /* Stash the option code in an array before parsing it. */ 2016 APR_ARRAY_PUSH(received_opts, int) = opt_id; 2017 2018 switch (opt_id) { 2019 case 'r': 2020 { 2021 if (opt_state.start_revision.kind != svn_opt_revision_unspecified) 2022 { 2023 err = svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL, 2024 _("Multiple revision arguments encountered; " 2025 "try '-r N:M' instead of '-r N -r M'")); 2026 return EXIT_ERROR(err); 2027 } 2028 if (svn_opt_parse_revision(&(opt_state.start_revision), 2029 &(opt_state.end_revision), 2030 opt_arg, pool) != 0) 2031 { 2032 err = svn_utf_cstring_to_utf8(&utf8_opt_arg, opt_arg, 2033 pool); 2034 2035 if (! err) 2036 err = svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL, 2037 _("Syntax error in revision argument '%s'"), 2038 utf8_opt_arg); 2039 return EXIT_ERROR(err); 2040 } 2041 } 2042 break; 2043 case 't': 2044 opt_state.txn_id = opt_arg; 2045 break; 2046 2047 case 'q': 2048 opt_state.quiet = TRUE; 2049 break; 2050 case 'h': 2051 case '?': 2052 opt_state.help = TRUE; 2053 break; 2054 case 'M': 2055 opt_state.memory_cache_size 2056 = 0x100000 * apr_strtoi64(opt_arg, NULL, 0); 2057 break; 2058 case 'F': 2059 SVN_INT_ERR(svn_utf_cstring_to_utf8(&utf8_opt_arg, opt_arg, pool)); 2060 SVN_INT_ERR(svn_stringbuf_from_file2(&(opt_state.filedata), 2061 utf8_opt_arg, pool)); 2062 dash_F_arg = TRUE; 2063 case svnadmin__version: 2064 opt_state.version = TRUE; 2065 break; 2066 case svnadmin__incremental: 2067 opt_state.incremental = TRUE; 2068 break; 2069 case svnadmin__deltas: 2070 opt_state.use_deltas = TRUE; 2071 break; 2072 case svnadmin__ignore_uuid: 2073 opt_state.uuid_action = svn_repos_load_uuid_ignore; 2074 break; 2075 case svnadmin__force_uuid: 2076 opt_state.uuid_action = svn_repos_load_uuid_force; 2077 break; 2078 case svnadmin__pre_1_4_compatible: 2079 opt_state.pre_1_4_compatible = TRUE; 2080 break; 2081 case svnadmin__pre_1_5_compatible: 2082 opt_state.pre_1_5_compatible = TRUE; 2083 break; 2084 case svnadmin__pre_1_6_compatible: 2085 opt_state.pre_1_6_compatible = TRUE; 2086 break; 2087 case svnadmin__compatible_version: 2088 { 2089 svn_version_t latest = { SVN_VER_MAJOR, SVN_VER_MINOR, 2090 SVN_VER_PATCH, NULL }; 2091 svn_version_t *compatible_version; 2092 2093 /* Parse the version string which carries our target 2094 compatibility. */ 2095 SVN_INT_ERR(svn_version__parse_version_string(&compatible_version, 2096 opt_arg, pool)); 2097 2098 /* We can't create repository with a version older than 1.0.0. */ 2099 if (! svn_version__at_least(compatible_version, 1, 0, 0)) 2100 { 2101 err = svn_error_createf(SVN_ERR_UNSUPPORTED_FEATURE, NULL, 2102 _("Cannot create pre-1.0-compatible " 2103 "repositories")); 2104 return EXIT_ERROR(err); 2105 } 2106 2107 /* We can't create repository with a version newer than what 2108 the running version of Subversion supports. */ 2109 if (! svn_version__at_least(&latest, 2110 compatible_version->major, 2111 compatible_version->minor, 2112 compatible_version->patch)) 2113 { 2114 err = svn_error_createf(SVN_ERR_UNSUPPORTED_FEATURE, NULL, 2115 _("Cannot guarantee compatibility " 2116 "beyond the current running version " 2117 "(%s)"), 2118 SVN_VER_NUM ); 2119 return EXIT_ERROR(err); 2120 } 2121 2122 opt_state.compatible_version = compatible_version; 2123 } 2124 break; 2125 case svnadmin__fs_type: 2126 SVN_INT_ERR(svn_utf_cstring_to_utf8(&opt_state.fs_type, opt_arg, pool)); 2127 break; 2128 case svnadmin__parent_dir: 2129 SVN_INT_ERR(svn_utf_cstring_to_utf8(&opt_state.parent_dir, opt_arg, 2130 pool)); 2131 opt_state.parent_dir 2132 = svn_dirent_internal_style(opt_state.parent_dir, pool); 2133 break; 2134 case svnadmin__use_pre_commit_hook: 2135 opt_state.use_pre_commit_hook = TRUE; 2136 break; 2137 case svnadmin__use_post_commit_hook: 2138 opt_state.use_post_commit_hook = TRUE; 2139 break; 2140 case svnadmin__use_pre_revprop_change_hook: 2141 opt_state.use_pre_revprop_change_hook = TRUE; 2142 break; 2143 case svnadmin__use_post_revprop_change_hook: 2144 opt_state.use_post_revprop_change_hook = TRUE; 2145 break; 2146 case svnadmin__bdb_txn_nosync: 2147 opt_state.bdb_txn_nosync = TRUE; 2148 break; 2149 case svnadmin__bdb_log_keep: 2150 opt_state.bdb_log_keep = TRUE; 2151 break; 2152 case svnadmin__bypass_hooks: 2153 opt_state.bypass_hooks = TRUE; 2154 break; 2155 case svnadmin__bypass_prop_validation: 2156 opt_state.bypass_prop_validation = TRUE; 2157 break; 2158 case svnadmin__clean_logs: 2159 opt_state.clean_logs = TRUE; 2160 break; 2161 case svnadmin__config_dir: 2162 SVN_INT_ERR(svn_utf_cstring_to_utf8(&utf8_opt_arg, opt_arg, pool)); 2163 opt_state.config_dir = 2164 apr_pstrdup(pool, svn_dirent_canonicalize(utf8_opt_arg, pool)); 2165 break; 2166 case svnadmin__wait: 2167 opt_state.wait = TRUE; 2168 break; 2169 default: 2170 { 2171 SVN_INT_ERR(subcommand_help(NULL, NULL, pool)); 2172 return EXIT_FAILURE; 2173 } 2174 } /* close `switch' */ 2175 } /* close `while' */ 2176 2177 /* If the user asked for help, then the rest of the arguments are 2178 the names of subcommands to get help on (if any), or else they're 2179 just typos/mistakes. Whatever the case, the subcommand to 2180 actually run is subcommand_help(). */ 2181 if (opt_state.help) 2182 subcommand = svn_opt_get_canonical_subcommand2(cmd_table, "help"); 2183 2184 /* If we're not running the `help' subcommand, then look for a 2185 subcommand in the first argument. */ 2186 if (subcommand == NULL) 2187 { 2188 if (os->ind >= os->argc) 2189 { 2190 if (opt_state.version) 2191 { 2192 /* Use the "help" subcommand to handle the "--version" option. */ 2193 static const svn_opt_subcommand_desc2_t pseudo_cmd = 2194 { "--version", subcommand_help, {0}, "", 2195 {svnadmin__version, /* must accept its own option */ 2196 'q', /* --quiet */ 2197 } }; 2198 2199 subcommand = &pseudo_cmd; 2200 } 2201 else 2202 { 2203 svn_error_clear(svn_cmdline_fprintf(stderr, pool, 2204 _("subcommand argument required\n"))); 2205 SVN_INT_ERR(subcommand_help(NULL, NULL, pool)); 2206 return EXIT_FAILURE; 2207 } 2208 } 2209 else 2210 { 2211 const char *first_arg = os->argv[os->ind++]; 2212 subcommand = svn_opt_get_canonical_subcommand2(cmd_table, first_arg); 2213 if (subcommand == NULL) 2214 { 2215 const char *first_arg_utf8; 2216 SVN_INT_ERR(svn_utf_cstring_to_utf8(&first_arg_utf8, 2217 first_arg, pool)); 2218 svn_error_clear( 2219 svn_cmdline_fprintf(stderr, pool, 2220 _("Unknown subcommand: '%s'\n"), 2221 first_arg_utf8)); 2222 SVN_INT_ERR(subcommand_help(NULL, NULL, pool)); 2223 return EXIT_FAILURE; 2224 } 2225 } 2226 } 2227 2228 /* Every subcommand except `help' and `freeze' with '-F' require a 2229 second argument -- the repository path. Parse it out here and 2230 store it in opt_state. */ 2231 if (!(subcommand->cmd_func == subcommand_help 2232 || (subcommand->cmd_func == subcommand_freeze && dash_F_arg))) 2233 { 2234 const char *repos_path = NULL; 2235 2236 if (os->ind >= os->argc) 2237 { 2238 err = svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL, 2239 _("Repository argument required")); 2240 return EXIT_ERROR(err); 2241 } 2242 2243 if ((err = svn_utf_cstring_to_utf8(&repos_path, 2244 os->argv[os->ind++], pool))) 2245 { 2246 return EXIT_ERROR(err); 2247 } 2248 2249 if (svn_path_is_url(repos_path)) 2250 { 2251 err = svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL, 2252 _("'%s' is a URL when it should be a " 2253 "local path"), repos_path); 2254 return EXIT_ERROR(err); 2255 } 2256 2257 opt_state.repository_path = svn_dirent_internal_style(repos_path, pool); 2258 } 2259 2260 /* Check that the subcommand wasn't passed any inappropriate options. */ 2261 for (i = 0; i < received_opts->nelts; i++) 2262 { 2263 opt_id = APR_ARRAY_IDX(received_opts, i, int); 2264 2265 /* All commands implicitly accept --help, so just skip over this 2266 when we see it. Note that we don't want to include this option 2267 in their "accepted options" list because it would be awfully 2268 redundant to display it in every commands' help text. */ 2269 if (opt_id == 'h' || opt_id == '?') 2270 continue; 2271 2272 if (! svn_opt_subcommand_takes_option3(subcommand, opt_id, NULL)) 2273 { 2274 const char *optstr; 2275 const apr_getopt_option_t *badopt = 2276 svn_opt_get_option_from_code2(opt_id, options_table, subcommand, 2277 pool); 2278 svn_opt_format_option(&optstr, badopt, FALSE, pool); 2279 if (subcommand->name[0] == '-') 2280 SVN_INT_ERR(subcommand_help(NULL, NULL, pool)); 2281 else 2282 svn_error_clear(svn_cmdline_fprintf(stderr, pool 2283 , _("Subcommand '%s' doesn't accept option '%s'\n" 2284 "Type 'svnadmin help %s' for usage.\n"), 2285 subcommand->name, optstr, subcommand->name)); 2286 return EXIT_FAILURE; 2287 } 2288 } 2289 2290 /* Set up our cancellation support. */ 2291 setup_cancellation_signals(signal_handler); 2292 2293#ifdef SIGPIPE 2294 /* Disable SIGPIPE generation for the platforms that have it. */ 2295 apr_signal(SIGPIPE, SIG_IGN); 2296#endif 2297 2298#ifdef SIGXFSZ 2299 /* Disable SIGXFSZ generation for the platforms that have it, otherwise 2300 * working with large files when compiled against an APR that doesn't have 2301 * large file support will crash the program, which is uncool. */ 2302 apr_signal(SIGXFSZ, SIG_IGN); 2303#endif 2304 2305 /* Configure FSFS caches for maximum efficiency with svnadmin. 2306 * Also, apply the respective command line parameters, if given. */ 2307 { 2308 svn_cache_config_t settings = *svn_cache_config_get(); 2309 2310 settings.cache_size = opt_state.memory_cache_size; 2311 settings.single_threaded = TRUE; 2312 2313 svn_cache_config_set(&settings); 2314 } 2315 2316 /* Run the subcommand. */ 2317 err = (*subcommand->cmd_func)(os, &opt_state, pool); 2318 if (err) 2319 { 2320 /* For argument-related problems, suggest using the 'help' 2321 subcommand. */ 2322 if (err->apr_err == SVN_ERR_CL_INSUFFICIENT_ARGS 2323 || err->apr_err == SVN_ERR_CL_ARG_PARSING_ERROR) 2324 { 2325 err = svn_error_quick_wrap(err, 2326 _("Try 'svnadmin help' for more info")); 2327 } 2328 return EXIT_ERROR(err); 2329 } 2330 else 2331 { 2332 /* Ensure that everything is written to stdout, so the user will 2333 see any print errors. */ 2334 err = svn_cmdline_fflush(stdout); 2335 if (err) 2336 { 2337 return EXIT_ERROR(err); 2338 } 2339 return EXIT_SUCCESS; 2340 } 2341} 2342 2343int 2344main(int argc, const char *argv[]) 2345{ 2346 apr_pool_t *pool; 2347 int exit_code; 2348 2349 /* Initialize the app. */ 2350 if (svn_cmdline_init("svnadmin", stderr) != EXIT_SUCCESS) 2351 return EXIT_FAILURE; 2352 2353 /* Create our top-level pool. Use a separate mutexless allocator, 2354 * given this application is single threaded. 2355 */ 2356 pool = apr_allocator_owner_get(svn_pool_create_allocator(FALSE)); 2357 2358 exit_code = sub_main(argc, argv, pool); 2359 2360 svn_pool_destroy(pool); 2361 return exit_code; 2362} 2363