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