]> CyberLeo.Net >> Repos - FreeBSD/stable/10.git/blob - contrib/subversion/subversion/svnfsfs/svnfsfs.c
MFC r275385 (by bapt):
[FreeBSD/stable/10.git] / contrib / subversion / subversion / svnfsfs / svnfsfs.c
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 \f
43 /*** Code. ***/
44
45 /* A flag to see if we've been cancelled by the client or not. */
46 static volatile sig_atomic_t cancelled = FALSE;
47
48 /* A signal handler to support cancellation. */
49 static void
50 signal_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. */
58 static void
59 setup_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
75 svn_error_t *
76 check_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. */
86 static void
87 warning_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 */
97 static svn_error_t *
98 check_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 \f
115 /** Subcommands. **/
116
117 enum 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  */
126 static 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  */
154 static 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
204 svn_error_t *
205 open_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'. */
227 svn_error_t *
228 subcommand__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 \f
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  */
258 static svn_error_t *
259 sub_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
511 int
512 main(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 }