2 * svnfsfs.c: FSFS repository manipulation tool main file.
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
13 * http://www.apache.org/licenses/LICENSE-2.0
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
21 * ====================================================================
24 #include <apr_signal.h>
26 #include "svn_pools.h"
27 #include "svn_cmdline.h"
31 #include "svn_dirent_uri.h"
32 #include "svn_repos.h"
33 #include "svn_cache_config.h"
34 #include "svn_version.h"
36 #include "private/svn_cmdline_private.h"
38 #include "svn_private_config.h"
45 /* A flag to see if we've been cancelled by the client or not. */
46 static volatile sig_atomic_t cancelled = FALSE;
48 /* A signal handler to support cancellation. */
50 signal_handler(int signum)
52 apr_signal(signum, SIG_IGN);
57 /* A helper to set up the cancellation signal handlers. */
59 setup_cancellation_signals(void (*handler)(int signum))
61 apr_signal(SIGINT, handler);
63 /* SIGBREAK is a Win32 specific signal generated by ctrl-break. */
64 apr_signal(SIGBREAK, handler);
67 apr_signal(SIGHUP, handler);
70 apr_signal(SIGTERM, handler);
76 check_cancel(void *baton)
79 return svn_error_create(SVN_ERR_CANCELLED, NULL, _("Caught signal"));
85 /* Custom filesystem warning function. */
87 warning_func(void *baton,
92 svn_handle_warning2(stderr, err, "svnfsfs: ");
96 /* Version compatibility check */
98 check_lib_versions(void)
100 static const svn_version_checklist_t checklist[] =
102 { "svn_subr", svn_subr_version },
103 { "svn_repos", svn_repos_version },
104 { "svn_fs", svn_fs_version },
105 { "svn_delta", svn_delta_version },
108 SVN_VERSION_DEFINE(my_version);
110 return svn_ver_check_list2(&my_version, checklist, svn_ver_equal);
117 enum svnfsfs__cmdline_options_t
119 svnfsfs__version = SVN_OPT_FIRST_LONGOPT_ID
122 /* Option codes and descriptions.
124 * The entire list must be terminated with an entry of nulls.
126 static const apr_getopt_option_t options_table[] =
129 N_("show help on a subcommand")},
132 N_("show help on a subcommand")},
134 {"version", svnfsfs__version, 0,
135 N_("show program version information")},
138 N_("no progress (only errors to stderr)")},
141 N_("specify revision number ARG (or X:Y range)")},
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.")},
151 /* Array of available subcommands.
152 * The entire list must be terminated with an entry of nulls.
154 static const svn_opt_subcommand_desc2_t cmd_table[] =
156 {"help", subcommand__help, {"?", "h"}, N_
157 ("usage: svnfsfs help [SUBCOMMAND...]\n\n"
158 "Describe the usage of this program or its subcommands.\n"),
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"),
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"),
195 {"stats", subcommand__stats, {0}, N_
196 ("usage: svnfsfs stats REPOS_PATH\n\n"
197 "Write object size statistics to console.\n"),
200 { NULL, NULL, {0}, NULL, {0} }
205 open_fs(svn_fs_t **fs,
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"),
220 SVN_ERR(svn_fs_open2(fs, path, NULL, pool, pool));
221 svn_fs_set_warning_func(*fs, warning_func, NULL);
226 /* This implements `svn_opt_subcommand_t'. */
228 subcommand__help(apr_getopt_t *os, void *baton, apr_pool_t *pool)
230 svnfsfs__opt_state *opt_state = baton;
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"
237 "Available subcommands:\n");
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,
244 header, cmd_table, options_table, NULL, NULL,
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.
259 sub_main(int *exit_code, int argc, const char *argv[], apr_pool_t *pool)
262 apr_status_t apr_err;
264 const svn_opt_subcommand_desc2_t *subcommand = NULL;
265 svnfsfs__opt_state opt_state = { 0 };
268 apr_array_header_t *received_opts;
271 received_opts = apr_array_make(pool, SVN_OPT_MAX_OPTIONS, sizeof(int));
273 /* Check library versions */
274 SVN_ERR(check_lib_versions());
276 /* Initialize the FS library. */
277 SVN_ERR(svn_fs_initialize(pool));
281 SVN_ERR(subcommand__help(NULL, NULL, pool));
282 *exit_code = EXIT_FAILURE;
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;
292 SVN_ERR(svn_cmdline__getopt_init(&os, argc, argv, pool));
299 const char *utf8_opt_arg;
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))
307 SVN_ERR(subcommand__help(NULL, NULL, pool));
308 *exit_code = EXIT_FAILURE;
312 /* Stash the option code in an array before parsing it. */
313 APR_ARRAY_PUSH(received_opts, int) = opt_id;
318 if (opt_state.start_revision.kind != svn_opt_revision_unspecified)
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'"));
324 if (svn_opt_parse_revision(&(opt_state.start_revision),
325 &(opt_state.end_revision),
328 SVN_ERR(svn_utf_cstring_to_utf8(&utf8_opt_arg, opt_arg, pool));
330 return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
331 _("Syntax error in revision argument '%s'"),
337 opt_state.quiet = TRUE;
341 opt_state.help = TRUE;
344 opt_state.memory_cache_size
345 = 0x100000 * apr_strtoi64(opt_arg, NULL, 0);
347 case svnfsfs__version:
348 opt_state.version = TRUE;
352 SVN_ERR(subcommand__help(NULL, NULL, pool));
353 *exit_code = EXIT_FAILURE;
356 } /* close `switch' */
357 } /* close `while' */
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(). */
364 subcommand = svn_opt_get_canonical_subcommand2(cmd_table, "help");
366 /* If we're not running the `help' subcommand, then look for a
367 subcommand in the first argument. */
368 if (subcommand == NULL)
370 if (os->ind >= os->argc)
372 if (opt_state.version)
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 */
381 subcommand = &pseudo_cmd;
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;
394 const char *first_arg = os->argv[os->ind++];
395 subcommand = svn_opt_get_canonical_subcommand2(cmd_table, first_arg);
396 if (subcommand == NULL)
398 const char *first_arg_utf8;
399 SVN_ERR(svn_utf_cstring_to_utf8(&first_arg_utf8,
402 svn_cmdline_fprintf(stderr, pool,
403 _("Unknown subcommand: '%s'\n"),
405 SVN_ERR(subcommand__help(NULL, NULL, pool));
406 *exit_code = EXIT_FAILURE;
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))
416 const char *repos_path = NULL;
418 if (os->ind >= os->argc)
420 return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
421 _("Repository argument required"));
424 SVN_ERR(svn_utf_cstring_to_utf8(&repos_path, os->argv[os->ind++], pool));
426 if (svn_path_is_url(repos_path))
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);
433 opt_state.repository_path = svn_dirent_internal_style(repos_path, pool);
436 /* Check that the subcommand wasn't passed any inappropriate options. */
437 for (i = 0; i < received_opts->nelts; i++)
439 opt_id = APR_ARRAY_IDX(received_opts, i, int);
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 == '?')
448 if (! svn_opt_subcommand_takes_option3(subcommand, opt_id, NULL))
451 const apr_getopt_option_t *badopt =
452 svn_opt_get_option_from_code2(opt_id, options_table, subcommand,
454 svn_opt_format_option(&optstr, badopt, FALSE, pool);
455 if (subcommand->name[0] == '-')
456 SVN_ERR(subcommand__help(NULL, NULL, pool));
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;
467 /* Set up our cancellation support. */
468 setup_cancellation_signals(signal_handler);
471 /* Disable SIGPIPE generation for the platforms that have it. */
472 apr_signal(SIGPIPE, SIG_IGN);
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);
482 /* Configure FSFS caches for maximum efficiency with svnfsfs.
483 * Also, apply the respective command line parameters, if given. */
485 svn_cache_config_t settings = *svn_cache_config_get();
487 settings.cache_size = opt_state.memory_cache_size;
488 settings.single_threaded = TRUE;
490 svn_cache_config_set(&settings);
493 /* Run the subcommand. */
494 err = (*subcommand->cmd_func)(os, &opt_state, pool);
497 /* For argument-related problems, suggest using the 'help'
499 if (err->apr_err == SVN_ERR_CL_INSUFFICIENT_ARGS
500 || err->apr_err == SVN_ERR_CL_ARG_PARSING_ERROR)
502 err = svn_error_quick_wrap(err,
503 _("Try 'svnfsfs help' for more info"));
512 main(int argc, const char *argv[])
515 int exit_code = EXIT_SUCCESS;
518 /* Initialize the app. */
519 if (svn_cmdline_init("svnfsfs", stderr) != EXIT_SUCCESS)
522 /* Create our top-level pool. Use a separate mutexless allocator,
523 * given this application is single threaded.
525 pool = apr_allocator_owner_get(svn_pool_create_allocator(FALSE));
527 err = sub_main(&exit_code, argc, argv, pool);
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));
535 exit_code = EXIT_FAILURE;
536 svn_cmdline_handle_exit_error(err, NULL, "svnfsfs: ");
539 svn_pool_destroy(pool);