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