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