svnfsfs.c revision 289180
1/* 2 * svnfsfs.c: FSFS repository manipulation 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#include <apr_signal.h> 25 26#include "svn_pools.h" 27#include "svn_cmdline.h" 28#include "svn_opt.h" 29#include "svn_utf.h" 30#include "svn_path.h" 31#include "svn_dirent_uri.h" 32#include "svn_repos.h" 33#include "svn_cache_config.h" 34#include "svn_version.h" 35 36#include "private/svn_cmdline_private.h" 37 38#include "svn_private_config.h" 39 40#include "svnfsfs.h" 41 42 43/*** Code. ***/ 44 45/* A flag to see if we've been cancelled by the client or not. */ 46static volatile sig_atomic_t cancelled = FALSE; 47 48/* A signal handler to support cancellation. */ 49static void 50signal_handler(int signum) 51{ 52 apr_signal(signum, SIG_IGN); 53 cancelled = TRUE; 54} 55 56 57/* A helper to set up the cancellation signal handlers. */ 58static void 59setup_cancellation_signals(void (*handler)(int signum)) 60{ 61 apr_signal(SIGINT, handler); 62#ifdef SIGBREAK 63 /* SIGBREAK is a Win32 specific signal generated by ctrl-break. */ 64 apr_signal(SIGBREAK, handler); 65#endif 66#ifdef SIGHUP 67 apr_signal(SIGHUP, handler); 68#endif 69#ifdef SIGTERM 70 apr_signal(SIGTERM, handler); 71#endif 72} 73 74 75svn_error_t * 76check_cancel(void *baton) 77{ 78 if (cancelled) 79 return svn_error_create(SVN_ERR_CANCELLED, NULL, _("Caught signal")); 80 else 81 return SVN_NO_ERROR; 82} 83 84 85/* Custom filesystem warning function. */ 86static void 87warning_func(void *baton, 88 svn_error_t *err) 89{ 90 if (! err) 91 return; 92 svn_handle_warning2(stderr, err, "svnfsfs: "); 93} 94 95 96/* Version compatibility check */ 97static svn_error_t * 98check_lib_versions(void) 99{ 100 static const svn_version_checklist_t checklist[] = 101 { 102 { "svn_subr", svn_subr_version }, 103 { "svn_repos", svn_repos_version }, 104 { "svn_fs", svn_fs_version }, 105 { "svn_delta", svn_delta_version }, 106 { NULL, NULL } 107 }; 108 SVN_VERSION_DEFINE(my_version); 109 110 return svn_ver_check_list2(&my_version, checklist, svn_ver_equal); 111} 112 113 114 115/** Subcommands. **/ 116 117enum svnfsfs__cmdline_options_t 118 { 119 svnfsfs__version = SVN_OPT_FIRST_LONGOPT_ID 120 }; 121 122/* Option codes and descriptions. 123 * 124 * The entire list must be terminated with an entry of nulls. 125 */ 126static const apr_getopt_option_t options_table[] = 127 { 128 {"help", 'h', 0, 129 N_("show help on a subcommand")}, 130 131 {NULL, '?', 0, 132 N_("show help on a subcommand")}, 133 134 {"version", svnfsfs__version, 0, 135 N_("show program version information")}, 136 137 {"quiet", 'q', 0, 138 N_("no progress (only errors to stderr)")}, 139 140 {"revision", 'r', 1, 141 N_("specify revision number ARG (or X:Y range)")}, 142 143 {"memory-cache-size", 'M', 1, 144 N_("size of the extra in-memory cache in MB used to\n" 145 " minimize redundant operations. Default: 16.")}, 146 147 {NULL} 148 }; 149 150 151/* Array of available subcommands. 152 * The entire list must be terminated with an entry of nulls. 153 */ 154static const svn_opt_subcommand_desc2_t cmd_table[] = 155{ 156 {"help", subcommand__help, {"?", "h"}, N_ 157 ("usage: svnfsfs help [SUBCOMMAND...]\n\n" 158 "Describe the usage of this program or its subcommands.\n"), 159 {0} }, 160 161 {"dump-index", subcommand__dump_index, {0}, N_ 162 ("usage: svnfsfs dump-index REPOS_PATH -r REV\n\n" 163 "Dump the index contents for the revision / pack file containing revision REV\n" 164 "to console. This is only available for FSFS format 7 (SVN 1.9+) repositories.\n" 165 "The table produced contains a header in the first line followed by one line\n" 166 "per index entry, ordered by location in the revision / pack file. Columns:\n\n" 167 " * Byte offset (hex) at which the item starts\n" 168 " * Length (hex) of the item in bytes\n" 169 " * Item type (string) is one of the following:\n\n" 170 " none ... Unused section. File contents shall be NULs.\n" 171 " frep ... File representation.\n" 172 " drep ... Directory representation.\n" 173 " fprop .. File property.\n" 174 " dprop .. Directory property.\n" 175 " node ... Node revision.\n" 176 " chgs ... Changed paths list.\n" 177 " rep .... Representation of unknown type. Should not be used.\n" 178 " ??? .... Invalid. Index data is corrupt.\n\n" 179 " The distinction between frep, drep, fprop and dprop is a mere internal\n" 180 " classification used for various optimizations and does not affect the\n" 181 " operational correctness.\n\n" 182 " * Revision that the item belongs to (decimal)\n" 183 " * Item number (decimal) within that revision\n" 184 " * Modified FNV1a checksum (8 hex digits)\n"), 185 {'r', 'M'} }, 186 187 {"load-index", subcommand__load_index, {0}, N_ 188 ("usage: svnfsfs load-index REPOS_PATH\n\n" 189 "Read index contents from console. The format is the same as produced by the\n" 190 "dump-index command, except that checksum as well as header are optional and will\n" 191 "be ignored. The data must cover the full revision / pack file; the revision\n" 192 "number is automatically extracted from input stream. No ordering is required.\n"), 193 {'M'} }, 194 195 {"stats", subcommand__stats, {0}, N_ 196 ("usage: svnfsfs stats REPOS_PATH\n\n" 197 "Write object size statistics to console.\n"), 198 {'M'} }, 199 200 { NULL, NULL, {0}, NULL, {0} } 201}; 202 203 204svn_error_t * 205open_fs(svn_fs_t **fs, 206 const char *path, 207 apr_pool_t *pool) 208{ 209 const char *fs_type; 210 211 /* Verify that we can handle the repository type. */ 212 path = svn_dirent_join(path, "db", pool); 213 SVN_ERR(svn_fs_type(&fs_type, path, pool)); 214 if (strcmp(fs_type, SVN_FS_TYPE_FSFS)) 215 return svn_error_createf(SVN_ERR_FS_UNSUPPORTED_TYPE, NULL, 216 _("%s repositories are not supported"), 217 fs_type); 218 219 /* Now open it. */ 220 SVN_ERR(svn_fs_open2(fs, path, NULL, pool, pool)); 221 svn_fs_set_warning_func(*fs, warning_func, NULL); 222 223 return SVN_NO_ERROR; 224} 225 226/* This implements `svn_opt_subcommand_t'. */ 227svn_error_t * 228subcommand__help(apr_getopt_t *os, void *baton, apr_pool_t *pool) 229{ 230 svnfsfs__opt_state *opt_state = baton; 231 const char *header = 232 _("general usage: svnfsfs SUBCOMMAND REPOS_PATH [ARGS & OPTIONS ...]\n" 233 "Subversion FSFS repository manipulation tool.\n" 234 "Type 'svnfsfs help <subcommand>' for help on a specific subcommand.\n" 235 "Type 'svnfsfs --version' to see the program version.\n" 236 "\n" 237 "Available subcommands:\n"); 238 239 SVN_ERR(svn_opt_print_help4(os, "svnfsfs", 240 opt_state ? opt_state->version : FALSE, 241 opt_state ? opt_state->quiet : FALSE, 242 /*###opt_state ? opt_state->verbose :*/ FALSE, 243 NULL, 244 header, cmd_table, options_table, NULL, NULL, 245 pool)); 246 247 return SVN_NO_ERROR; 248} 249 250 251/** Main. **/ 252 253/* 254 * On success, leave *EXIT_CODE untouched and return SVN_NO_ERROR. On error, 255 * either return an error to be displayed, or set *EXIT_CODE to non-zero and 256 * return SVN_NO_ERROR. 257 */ 258static svn_error_t * 259sub_main(int *exit_code, int argc, const char *argv[], apr_pool_t *pool) 260{ 261 svn_error_t *err; 262 apr_status_t apr_err; 263 264 const svn_opt_subcommand_desc2_t *subcommand = NULL; 265 svnfsfs__opt_state opt_state = { 0 }; 266 apr_getopt_t *os; 267 int opt_id; 268 apr_array_header_t *received_opts; 269 int i; 270 271 received_opts = apr_array_make(pool, SVN_OPT_MAX_OPTIONS, sizeof(int)); 272 273 /* Check library versions */ 274 SVN_ERR(check_lib_versions()); 275 276 /* Initialize the FS library. */ 277 SVN_ERR(svn_fs_initialize(pool)); 278 279 if (argc <= 1) 280 { 281 SVN_ERR(subcommand__help(NULL, NULL, pool)); 282 *exit_code = EXIT_FAILURE; 283 return SVN_NO_ERROR; 284 } 285 286 /* Initialize opt_state. */ 287 opt_state.start_revision.kind = svn_opt_revision_unspecified; 288 opt_state.end_revision.kind = svn_opt_revision_unspecified; 289 opt_state.memory_cache_size = svn_cache_config_get()->cache_size; 290 291 /* Parse options. */ 292 SVN_ERR(svn_cmdline__getopt_init(&os, argc, argv, pool)); 293 294 os->interleave = 1; 295 296 while (1) 297 { 298 const char *opt_arg; 299 const char *utf8_opt_arg; 300 301 /* Parse the next option. */ 302 apr_err = apr_getopt_long(os, options_table, &opt_id, &opt_arg); 303 if (APR_STATUS_IS_EOF(apr_err)) 304 break; 305 else if (apr_err) 306 { 307 SVN_ERR(subcommand__help(NULL, NULL, pool)); 308 *exit_code = EXIT_FAILURE; 309 return SVN_NO_ERROR; 310 } 311 312 /* Stash the option code in an array before parsing it. */ 313 APR_ARRAY_PUSH(received_opts, int) = opt_id; 314 315 switch (opt_id) { 316 case 'r': 317 { 318 if (opt_state.start_revision.kind != svn_opt_revision_unspecified) 319 { 320 return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL, 321 _("Multiple revision arguments encountered; " 322 "try '-r N:M' instead of '-r N -r M'")); 323 } 324 if (svn_opt_parse_revision(&(opt_state.start_revision), 325 &(opt_state.end_revision), 326 opt_arg, pool) != 0) 327 { 328 SVN_ERR(svn_utf_cstring_to_utf8(&utf8_opt_arg, opt_arg, pool)); 329 330 return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL, 331 _("Syntax error in revision argument '%s'"), 332 utf8_opt_arg); 333 } 334 } 335 break; 336 case 'q': 337 opt_state.quiet = TRUE; 338 break; 339 case 'h': 340 case '?': 341 opt_state.help = TRUE; 342 break; 343 case 'M': 344 opt_state.memory_cache_size 345 = 0x100000 * apr_strtoi64(opt_arg, NULL, 0); 346 break; 347 case svnfsfs__version: 348 opt_state.version = TRUE; 349 break; 350 default: 351 { 352 SVN_ERR(subcommand__help(NULL, NULL, pool)); 353 *exit_code = EXIT_FAILURE; 354 return SVN_NO_ERROR; 355 } 356 } /* close `switch' */ 357 } /* close `while' */ 358 359 /* If the user asked for help, then the rest of the arguments are 360 the names of subcommands to get help on (if any), or else they're 361 just typos/mistakes. Whatever the case, the subcommand to 362 actually run is subcommand_help(). */ 363 if (opt_state.help) 364 subcommand = svn_opt_get_canonical_subcommand2(cmd_table, "help"); 365 366 /* If we're not running the `help' subcommand, then look for a 367 subcommand in the first argument. */ 368 if (subcommand == NULL) 369 { 370 if (os->ind >= os->argc) 371 { 372 if (opt_state.version) 373 { 374 /* Use the "help" subcommand to handle the "--version" option. */ 375 static const svn_opt_subcommand_desc2_t pseudo_cmd = 376 { "--version", subcommand__help, {0}, "", 377 {svnfsfs__version, /* must accept its own option */ 378 'q', /* --quiet */ 379 } }; 380 381 subcommand = &pseudo_cmd; 382 } 383 else 384 { 385 svn_error_clear(svn_cmdline_fprintf(stderr, pool, 386 _("subcommand argument required\n"))); 387 SVN_ERR(subcommand__help(NULL, NULL, pool)); 388 *exit_code = EXIT_FAILURE; 389 return SVN_NO_ERROR; 390 } 391 } 392 else 393 { 394 const char *first_arg = os->argv[os->ind++]; 395 subcommand = svn_opt_get_canonical_subcommand2(cmd_table, first_arg); 396 if (subcommand == NULL) 397 { 398 const char *first_arg_utf8; 399 SVN_ERR(svn_utf_cstring_to_utf8(&first_arg_utf8, 400 first_arg, pool)); 401 svn_error_clear( 402 svn_cmdline_fprintf(stderr, pool, 403 _("Unknown subcommand: '%s'\n"), 404 first_arg_utf8)); 405 SVN_ERR(subcommand__help(NULL, NULL, pool)); 406 *exit_code = EXIT_FAILURE; 407 return SVN_NO_ERROR; 408 } 409 } 410 } 411 412 /* Every subcommand except `help' requires a second argument -- the 413 repository path. Parse it out here and store it in opt_state. */ 414 if (!(subcommand->cmd_func == subcommand__help)) 415 { 416 const char *repos_path = NULL; 417 418 if (os->ind >= os->argc) 419 { 420 return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL, 421 _("Repository argument required")); 422 } 423 424 SVN_ERR(svn_utf_cstring_to_utf8(&repos_path, os->argv[os->ind++], pool)); 425 426 if (svn_path_is_url(repos_path)) 427 { 428 return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL, 429 _("'%s' is a URL when it should be a " 430 "local path"), repos_path); 431 } 432 433 opt_state.repository_path = svn_dirent_internal_style(repos_path, pool); 434 } 435 436 /* Check that the subcommand wasn't passed any inappropriate options. */ 437 for (i = 0; i < received_opts->nelts; i++) 438 { 439 opt_id = APR_ARRAY_IDX(received_opts, i, int); 440 441 /* All commands implicitly accept --help, so just skip over this 442 when we see it. Note that we don't want to include this option 443 in their "accepted options" list because it would be awfully 444 redundant to display it in every commands' help text. */ 445 if (opt_id == 'h' || opt_id == '?') 446 continue; 447 448 if (! svn_opt_subcommand_takes_option3(subcommand, opt_id, NULL)) 449 { 450 const char *optstr; 451 const apr_getopt_option_t *badopt = 452 svn_opt_get_option_from_code2(opt_id, options_table, subcommand, 453 pool); 454 svn_opt_format_option(&optstr, badopt, FALSE, pool); 455 if (subcommand->name[0] == '-') 456 SVN_ERR(subcommand__help(NULL, NULL, pool)); 457 else 458 svn_error_clear(svn_cmdline_fprintf(stderr, pool 459 , _("Subcommand '%s' doesn't accept option '%s'\n" 460 "Type 'svnfsfs help %s' for usage.\n"), 461 subcommand->name, optstr, subcommand->name)); 462 *exit_code = EXIT_FAILURE; 463 return SVN_NO_ERROR; 464 } 465 } 466 467 /* Set up our cancellation support. */ 468 setup_cancellation_signals(signal_handler); 469 470#ifdef SIGPIPE 471 /* Disable SIGPIPE generation for the platforms that have it. */ 472 apr_signal(SIGPIPE, SIG_IGN); 473#endif 474 475#ifdef SIGXFSZ 476 /* Disable SIGXFSZ generation for the platforms that have it, otherwise 477 * working with large files when compiled against an APR that doesn't have 478 * large file support will crash the program, which is uncool. */ 479 apr_signal(SIGXFSZ, SIG_IGN); 480#endif 481 482 /* Configure FSFS caches for maximum efficiency with svnfsfs. 483 * Also, apply the respective command line parameters, if given. */ 484 { 485 svn_cache_config_t settings = *svn_cache_config_get(); 486 487 settings.cache_size = opt_state.memory_cache_size; 488 settings.single_threaded = TRUE; 489 490 svn_cache_config_set(&settings); 491 } 492 493 /* Run the subcommand. */ 494 err = (*subcommand->cmd_func)(os, &opt_state, pool); 495 if (err) 496 { 497 /* For argument-related problems, suggest using the 'help' 498 subcommand. */ 499 if (err->apr_err == SVN_ERR_CL_INSUFFICIENT_ARGS 500 || err->apr_err == SVN_ERR_CL_ARG_PARSING_ERROR) 501 { 502 err = svn_error_quick_wrap(err, 503 _("Try 'svnfsfs help' for more info")); 504 } 505 return err; 506 } 507 508 return SVN_NO_ERROR; 509} 510 511int 512main(int argc, const char *argv[]) 513{ 514 apr_pool_t *pool; 515 int exit_code = EXIT_SUCCESS; 516 svn_error_t *err; 517 518 /* Initialize the app. */ 519 if (svn_cmdline_init("svnfsfs", stderr) != EXIT_SUCCESS) 520 return EXIT_FAILURE; 521 522 /* Create our top-level pool. Use a separate mutexless allocator, 523 * given this application is single threaded. 524 */ 525 pool = apr_allocator_owner_get(svn_pool_create_allocator(FALSE)); 526 527 err = sub_main(&exit_code, argc, argv, pool); 528 529 /* Flush stdout and report if it fails. It would be flushed on exit anyway 530 but this makes sure that output is not silently lost if it fails. */ 531 err = svn_error_compose_create(err, svn_cmdline_fflush(stdout)); 532 533 if (err) 534 { 535 exit_code = EXIT_FAILURE; 536 svn_cmdline_handle_exit_error(err, NULL, "svnfsfs: "); 537 } 538 539 svn_pool_destroy(pool); 540 return exit_code; 541} 542