| /* |
| * svnfsfs.c: FSFS repository manipulation tool main file. |
| * |
| * ==================================================================== |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you under the Apache License, Version 2.0 (the |
| * "License"); you may not use this file except in compliance |
| * with the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, |
| * software distributed under the License is distributed on an |
| * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| * KIND, either express or implied. See the License for the |
| * specific language governing permissions and limitations |
| * under the License. |
| * ==================================================================== |
| */ |
| |
| #include "svn_pools.h" |
| #include "svn_cmdline.h" |
| #include "svn_opt.h" |
| #include "svn_utf.h" |
| #include "svn_path.h" |
| #include "svn_dirent_uri.h" |
| #include "svn_repos.h" |
| #include "svn_cache_config.h" |
| #include "svn_version.h" |
| |
| #include "private/svn_cmdline_private.h" |
| |
| #include "svn_private_config.h" |
| |
| #include "svnfsfs.h" |
| |
| |
| /*** Code. ***/ |
| |
| svn_cancel_func_t check_cancel = NULL; |
| |
| /* Custom filesystem warning function. */ |
| static void |
| warning_func(void *baton, |
| svn_error_t *err) |
| { |
| if (! err) |
| return; |
| svn_handle_warning2(stderr, err, "svnfsfs: "); |
| } |
| |
| |
| /* Version compatibility check */ |
| static svn_error_t * |
| check_lib_versions(void) |
| { |
| static const svn_version_checklist_t checklist[] = |
| { |
| { "svn_subr", svn_subr_version }, |
| { "svn_repos", svn_repos_version }, |
| { "svn_fs", svn_fs_version }, |
| { "svn_delta", svn_delta_version }, |
| { NULL, NULL } |
| }; |
| SVN_VERSION_DEFINE(my_version); |
| |
| return svn_ver_check_list2(&my_version, checklist, svn_ver_equal); |
| } |
| |
| |
| |
| /** Subcommands. **/ |
| |
| enum svnfsfs__cmdline_options_t |
| { |
| svnfsfs__version = SVN_OPT_FIRST_LONGOPT_ID |
| }; |
| |
| /* Option codes and descriptions. |
| * |
| * The entire list must be terminated with an entry of nulls. |
| */ |
| static const apr_getopt_option_t options_table[] = |
| { |
| {"help", 'h', 0, |
| N_("show help on a subcommand")}, |
| |
| {NULL, '?', 0, |
| N_("show help on a subcommand")}, |
| |
| {"version", svnfsfs__version, 0, |
| N_("show program version information")}, |
| |
| {"quiet", 'q', 0, |
| N_("no progress (only errors to stderr)")}, |
| |
| {"revision", 'r', 1, |
| N_("specify revision number ARG (or X:Y range)")}, |
| |
| {"memory-cache-size", 'M', 1, |
| N_("size of the extra in-memory cache in MB used to\n" |
| " minimize redundant operations. Default: 16.")}, |
| |
| {NULL} |
| }; |
| |
| |
| /* Array of available subcommands. |
| * The entire list must be terminated with an entry of nulls. |
| */ |
| static const svn_opt_subcommand_desc3_t cmd_table[] = |
| { |
| {"help", subcommand__help, {"?", "h"}, {N_( |
| "usage: svnfsfs help [SUBCOMMAND...]\n" |
| "\n"), N_( |
| "Describe the usage of this program or its subcommands.\n" |
| )}, |
| {0} }, |
| |
| {"dump-index", subcommand__dump_index, {0}, {N_( |
| "usage: svnfsfs dump-index REPOS_PATH -r REV\n" |
| "\n"), N_( |
| "Dump the index contents for the revision / pack file containing revision REV\n" |
| "to console. This is only available for FSFS format 7 (SVN 1.9+) repositories.\n" |
| "The table produced contains a header in the first line followed by one line\n" |
| "per index entry, ordered by location in the revision / pack file. Columns:\n" |
| "\n"), N_( |
| " * Byte offset (hex) at which the item starts\n" |
| " * Length (hex) of the item in bytes\n" |
| " * Item type (string) is one of the following:\n" |
| "\n"), N_( |
| " none ... Unused section. File contents shall be NULs.\n" |
| " frep ... File representation.\n" |
| " drep ... Directory representation.\n" |
| " fprop .. File property.\n" |
| " dprop .. Directory property.\n" |
| " node ... Node revision.\n" |
| " chgs ... Changed paths list.\n" |
| " rep .... Representation of unknown type. Should not be used.\n" |
| " ??? .... Invalid. Index data is corrupt.\n" |
| "\n"), N_( |
| " The distinction between frep, drep, fprop and dprop is a mere internal\n" |
| " classification used for various optimizations and does not affect the\n" |
| " operational correctness.\n" |
| "\n"), N_( |
| " * Revision that the item belongs to (decimal)\n" |
| " * Item number (decimal) within that revision\n" |
| " * Modified FNV1a checksum (8 hex digits)\n" |
| )}, |
| {'r', 'M'} }, |
| |
| {"load-index", subcommand__load_index, {0}, {N_( |
| "usage: svnfsfs load-index REPOS_PATH\n" |
| "\n"), N_( |
| "Read index contents from console. The format is the same as produced by the\n" |
| "dump-index command, except that checksum as well as header are optional and will\n" |
| "be ignored. The data must cover the full revision / pack file; the revision\n" |
| "number is automatically extracted from input stream. No ordering is required.\n" |
| )}, |
| {'M'} }, |
| |
| {"stats", subcommand__stats, {0}, {N_( |
| "usage: svnfsfs stats REPOS_PATH\n" |
| "\n"), N_( |
| "Write object size statistics to console.\n" |
| )}, |
| {'M'} }, |
| |
| { NULL, NULL, {0}, {NULL}, {0} } |
| }; |
| |
| |
| svn_error_t * |
| open_fs(svn_fs_t **fs, |
| const char *path, |
| apr_pool_t *pool) |
| { |
| const char *fs_type; |
| |
| /* Verify that we can handle the repository type. */ |
| path = svn_dirent_join(path, "db", pool); |
| SVN_ERR(svn_fs_type(&fs_type, path, pool)); |
| if (strcmp(fs_type, SVN_FS_TYPE_FSFS)) |
| return svn_error_createf(SVN_ERR_FS_UNSUPPORTED_TYPE, NULL, |
| _("%s repositories are not supported"), |
| fs_type); |
| |
| /* Now open it. */ |
| SVN_ERR(svn_fs_open2(fs, path, NULL, pool, pool)); |
| svn_fs_set_warning_func(*fs, warning_func, NULL); |
| |
| return SVN_NO_ERROR; |
| } |
| |
| /* This implements `svn_opt_subcommand_t'. */ |
| svn_error_t * |
| subcommand__help(apr_getopt_t *os, void *baton, apr_pool_t *pool) |
| { |
| svnfsfs__opt_state *opt_state = baton; |
| const char *header = |
| _("general usage: svnfsfs SUBCOMMAND REPOS_PATH [ARGS & OPTIONS ...]\n" |
| "Subversion FSFS repository manipulation tool.\n" |
| "Type 'svnfsfs help <subcommand>' for help on a specific subcommand.\n" |
| "Type 'svnfsfs --version' to see the program version.\n" |
| "\n" |
| "Available subcommands:\n"); |
| |
| SVN_ERR(svn_opt_print_help5(os, "svnfsfs", |
| opt_state ? opt_state->version : FALSE, |
| opt_state ? opt_state->quiet : FALSE, |
| /*###opt_state ? opt_state->verbose :*/ FALSE, |
| NULL, |
| header, cmd_table, options_table, NULL, NULL, |
| pool)); |
| |
| return SVN_NO_ERROR; |
| } |
| |
| |
| /** Main. **/ |
| |
| /* |
| * On success, leave *EXIT_CODE untouched and return SVN_NO_ERROR. On error, |
| * either return an error to be displayed, or set *EXIT_CODE to non-zero and |
| * return SVN_NO_ERROR. |
| */ |
| static svn_error_t * |
| sub_main(int *exit_code, int argc, const char *argv[], apr_pool_t *pool) |
| { |
| svn_error_t *err; |
| apr_status_t apr_err; |
| |
| const svn_opt_subcommand_desc3_t *subcommand = NULL; |
| svnfsfs__opt_state opt_state = { 0 }; |
| apr_getopt_t *os; |
| int opt_id; |
| apr_array_header_t *received_opts; |
| int i; |
| |
| received_opts = apr_array_make(pool, SVN_OPT_MAX_OPTIONS, sizeof(int)); |
| |
| /* Check library versions */ |
| SVN_ERR(check_lib_versions()); |
| |
| /* Initialize the FS library. */ |
| SVN_ERR(svn_fs_initialize(pool)); |
| |
| if (argc <= 1) |
| { |
| SVN_ERR(subcommand__help(NULL, NULL, pool)); |
| *exit_code = EXIT_FAILURE; |
| return SVN_NO_ERROR; |
| } |
| |
| /* Initialize opt_state. */ |
| opt_state.start_revision.kind = svn_opt_revision_unspecified; |
| opt_state.end_revision.kind = svn_opt_revision_unspecified; |
| opt_state.memory_cache_size = svn_cache_config_get()->cache_size; |
| |
| /* Parse options. */ |
| SVN_ERR(svn_cmdline__getopt_init(&os, argc, argv, pool)); |
| |
| os->interleave = 1; |
| |
| while (1) |
| { |
| const char *opt_arg; |
| const char *utf8_opt_arg; |
| |
| /* Parse the next option. */ |
| apr_err = apr_getopt_long(os, options_table, &opt_id, &opt_arg); |
| if (APR_STATUS_IS_EOF(apr_err)) |
| break; |
| else if (apr_err) |
| { |
| SVN_ERR(subcommand__help(NULL, NULL, pool)); |
| *exit_code = EXIT_FAILURE; |
| return SVN_NO_ERROR; |
| } |
| |
| /* Stash the option code in an array before parsing it. */ |
| APR_ARRAY_PUSH(received_opts, int) = opt_id; |
| |
| switch (opt_id) { |
| case 'r': |
| { |
| if (opt_state.start_revision.kind != svn_opt_revision_unspecified) |
| { |
| return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL, |
| _("Multiple revision arguments encountered; " |
| "try '-r N:M' instead of '-r N -r M'")); |
| } |
| if (svn_opt_parse_revision(&(opt_state.start_revision), |
| &(opt_state.end_revision), |
| opt_arg, pool) != 0) |
| { |
| SVN_ERR(svn_utf_cstring_to_utf8(&utf8_opt_arg, opt_arg, pool)); |
| |
| return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL, |
| _("Syntax error in revision argument '%s'"), |
| utf8_opt_arg); |
| } |
| } |
| break; |
| case 'q': |
| opt_state.quiet = TRUE; |
| break; |
| case 'h': |
| case '?': |
| opt_state.help = TRUE; |
| break; |
| case 'M': |
| { |
| apr_uint64_t sz_val; |
| SVN_ERR(svn_cstring_atoui64(&sz_val, opt_arg)); |
| |
| opt_state.memory_cache_size = 0x100000 * sz_val; |
| } |
| break; |
| case svnfsfs__version: |
| opt_state.version = TRUE; |
| break; |
| default: |
| { |
| SVN_ERR(subcommand__help(NULL, NULL, pool)); |
| *exit_code = EXIT_FAILURE; |
| return SVN_NO_ERROR; |
| } |
| } /* close `switch' */ |
| } /* close `while' */ |
| |
| /* If the user asked for help, then the rest of the arguments are |
| the names of subcommands to get help on (if any), or else they're |
| just typos/mistakes. Whatever the case, the subcommand to |
| actually run is subcommand_help(). */ |
| if (opt_state.help) |
| subcommand = svn_opt_get_canonical_subcommand3(cmd_table, "help"); |
| |
| /* If we're not running the `help' subcommand, then look for a |
| subcommand in the first argument. */ |
| if (subcommand == NULL) |
| { |
| if (os->ind >= os->argc) |
| { |
| if (opt_state.version) |
| { |
| /* Use the "help" subcommand to handle the "--version" option. */ |
| static const svn_opt_subcommand_desc3_t pseudo_cmd = |
| { "--version", subcommand__help, {0}, {""}, |
| {svnfsfs__version, /* must accept its own option */ |
| 'q', /* --quiet */ |
| } }; |
| |
| subcommand = &pseudo_cmd; |
| } |
| else |
| { |
| svn_error_clear(svn_cmdline_fprintf(stderr, pool, |
| _("subcommand argument required\n"))); |
| SVN_ERR(subcommand__help(NULL, NULL, pool)); |
| *exit_code = EXIT_FAILURE; |
| return SVN_NO_ERROR; |
| } |
| } |
| else |
| { |
| const char *first_arg; |
| |
| SVN_ERR(svn_utf_cstring_to_utf8(&first_arg, os->argv[os->ind++], |
| pool)); |
| subcommand = svn_opt_get_canonical_subcommand3(cmd_table, first_arg); |
| if (subcommand == NULL) |
| { |
| svn_error_clear( |
| svn_cmdline_fprintf(stderr, pool, |
| _("Unknown subcommand: '%s'\n"), |
| first_arg)); |
| SVN_ERR(subcommand__help(NULL, NULL, pool)); |
| *exit_code = EXIT_FAILURE; |
| return SVN_NO_ERROR; |
| } |
| } |
| } |
| |
| /* Every subcommand except `help' requires a second argument -- the |
| repository path. Parse it out here and store it in opt_state. */ |
| if (!(subcommand->cmd_func == subcommand__help)) |
| { |
| const char *repos_path = NULL; |
| |
| if (os->ind >= os->argc) |
| { |
| return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL, |
| _("Repository argument required")); |
| } |
| |
| SVN_ERR(svn_utf_cstring_to_utf8(&repos_path, os->argv[os->ind++], pool)); |
| |
| if (svn_path_is_url(repos_path)) |
| { |
| return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL, |
| _("'%s' is a URL when it should be a " |
| "local path"), repos_path); |
| } |
| |
| opt_state.repository_path = svn_dirent_internal_style(repos_path, pool); |
| } |
| |
| /* Check that the subcommand wasn't passed any inappropriate options. */ |
| for (i = 0; i < received_opts->nelts; i++) |
| { |
| opt_id = APR_ARRAY_IDX(received_opts, i, int); |
| |
| /* All commands implicitly accept --help, so just skip over this |
| when we see it. Note that we don't want to include this option |
| in their "accepted options" list because it would be awfully |
| redundant to display it in every commands' help text. */ |
| if (opt_id == 'h' || opt_id == '?') |
| continue; |
| |
| if (! svn_opt_subcommand_takes_option4(subcommand, opt_id, NULL)) |
| { |
| const char *optstr; |
| const apr_getopt_option_t *badopt = |
| svn_opt_get_option_from_code3(opt_id, options_table, subcommand, |
| pool); |
| svn_opt_format_option(&optstr, badopt, FALSE, pool); |
| if (subcommand->name[0] == '-') |
| SVN_ERR(subcommand__help(NULL, NULL, pool)); |
| else |
| svn_error_clear(svn_cmdline_fprintf(stderr, pool |
| , _("Subcommand '%s' doesn't accept option '%s'\n" |
| "Type 'svnfsfs help %s' for usage.\n"), |
| subcommand->name, optstr, subcommand->name)); |
| *exit_code = EXIT_FAILURE; |
| return SVN_NO_ERROR; |
| } |
| } |
| |
| /* Set up our cancellation support. */ |
| check_cancel = svn_cmdline__setup_cancellation_handler(); |
| |
| /* Configure FSFS caches for maximum efficiency with svnfsfs. |
| * Also, apply the respective command line parameters, if given. */ |
| { |
| svn_cache_config_t settings = *svn_cache_config_get(); |
| |
| settings.cache_size = opt_state.memory_cache_size; |
| settings.single_threaded = TRUE; |
| |
| svn_cache_config_set(&settings); |
| } |
| |
| /* Run the subcommand. */ |
| err = (*subcommand->cmd_func)(os, &opt_state, pool); |
| if (err) |
| { |
| /* For argument-related problems, suggest using the 'help' |
| subcommand. */ |
| if (err->apr_err == SVN_ERR_CL_INSUFFICIENT_ARGS |
| || err->apr_err == SVN_ERR_CL_ARG_PARSING_ERROR) |
| { |
| err = svn_error_quick_wrap(err, |
| _("Try 'svnfsfs help' for more info")); |
| } |
| return err; |
| } |
| |
| return SVN_NO_ERROR; |
| } |
| |
| int |
| main(int argc, const char *argv[]) |
| { |
| apr_pool_t *pool; |
| int exit_code = EXIT_SUCCESS; |
| svn_error_t *err; |
| |
| /* Initialize the app. */ |
| if (svn_cmdline_init("svnfsfs", stderr) != EXIT_SUCCESS) |
| return EXIT_FAILURE; |
| |
| /* Create our top-level pool. Use a separate mutexless allocator, |
| * given this application is single threaded. |
| */ |
| pool = apr_allocator_owner_get(svn_pool_create_allocator(FALSE)); |
| |
| err = sub_main(&exit_code, argc, argv, pool); |
| |
| /* Flush stdout and report if it fails. It would be flushed on exit anyway |
| but this makes sure that output is not silently lost if it fails. */ |
| err = svn_error_compose_create(err, svn_cmdline_fflush(stdout)); |
| |
| if (err) |
| { |
| exit_code = EXIT_FAILURE; |
| svn_cmdline_handle_exit_error(err, NULL, "svnfsfs: "); |
| } |
| |
| svn_pool_destroy(pool); |
| |
| svn_cmdline__cancellation_exit(); |
| |
| return exit_code; |
| } |