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