Put the main() and wmain() routines for CLI programs into a separate file.
[metze/wireshark/wip.git] / mergecap.c
1 /* Combine dump files, either by appending or by merging by timestamp
2  *
3  * Wireshark - Network traffic analyzer
4  * By Gerald Combs <gerald@wireshark.org>
5  * Copyright 1998 Gerald Combs
6  *
7  * SPDX-License-Identifier: GPL-2.0-or-later
8  *
9  * Mergecap written by Scott Renfro <scott@renfro.org> based on
10  * editcap by Richard Sharpe and Guy Harris
11  *
12  */
13
14 #include <config.h>
15
16 #include <stdio.h>
17 #include <stdlib.h>
18 #include <errno.h>
19 #include <glib.h>
20
21 #ifdef HAVE_GETOPT_H
22 #include <getopt.h>
23 #endif
24
25 #include <string.h>
26
27 #include <wiretap/wtap.h>
28
29 #ifndef HAVE_GETOPT_LONG
30 #include <wsutil/wsgetopt.h>
31 #endif
32
33 #include <wsutil/clopts_common.h>
34 #include <wsutil/cmdarg_err.h>
35 #include <wsutil/crash_info.h>
36 #include <wsutil/filesystem.h>
37 #include <wsutil/file_util.h>
38 #include <wsutil/privileges.h>
39 #include <wsutil/strnatcmp.h>
40
41 #include <cli_main.h>
42 #include <version_info.h>
43
44 #ifdef HAVE_PLUGINS
45 #include <wsutil/plugins.h>
46 #endif
47
48 #include <wsutil/report_message.h>
49
50 #include <wiretap/merge.h>
51
52 #include "ui/failure_message.h"
53
54 /*
55  * Show the usage
56  */
57 static void
58 print_usage(FILE *output)
59 {
60   fprintf(output, "\n");
61   fprintf(output, "Usage: mergecap [options] -w <outfile>|- <infile> [<infile> ...]\n");
62   fprintf(output, "\n");
63   fprintf(output, "Output:\n");
64   fprintf(output, "  -a                concatenate rather than merge files.\n");
65   fprintf(output, "                    default is to merge based on frame timestamps.\n");
66   fprintf(output, "  -s <snaplen>      truncate packets to <snaplen> bytes of data.\n");
67   fprintf(output, "  -w <outfile>|-    set the output filename to <outfile> or '-' for stdout.\n");
68 #ifdef PCAP_NG_DEFAULT
69   fprintf(output, "  -F <capture type> set the output file type; default is pcapng.\n");
70 #else
71   fprintf(output, "  -F <capture type> set the output file type; default is pcap.\n");
72 #endif
73   fprintf(output, "                    an empty \"-F\" option will list the file types.\n");
74   fprintf(output, "  -I <IDB merge mode> set the merge mode for Interface Description Blocks; default is 'all'.\n");
75   fprintf(output, "                    an empty \"-I\" option will list the merge modes.\n");
76   fprintf(output, "\n");
77   fprintf(output, "Miscellaneous:\n");
78   fprintf(output, "  -h                display this help and exit.\n");
79   fprintf(output, "  -v                verbose output.\n");
80 }
81
82 /*
83  * Report an error in command-line arguments.
84  */
85 static void
86 mergecap_cmdarg_err(const char *fmt, va_list ap)
87 {
88   fprintf(stderr, "mergecap: ");
89   vfprintf(stderr, fmt, ap);
90   fprintf(stderr, "\n");
91 }
92
93 /*
94  * Report additional information for an error in command-line arguments.
95  */
96 static void
97 mergecap_cmdarg_err_cont(const char *fmt, va_list ap)
98 {
99   vfprintf(stderr, fmt, ap);
100   fprintf(stderr, "\n");
101 }
102
103 struct string_elem {
104   const char *sstr;     /* The short string */
105   const char *lstr;     /* The long string */
106 };
107
108 static gint
109 string_compare(gconstpointer a, gconstpointer b)
110 {
111   return strcmp(((const struct string_elem *)a)->sstr,
112                 ((const struct string_elem *)b)->sstr);
113 }
114
115 static void
116 string_elem_print(gpointer data, gpointer not_used _U_)
117 {
118   fprintf(stderr, "    %s - %s\n", ((struct string_elem *)data)->sstr,
119           ((struct string_elem *)data)->lstr);
120 }
121
122 /*
123  * General errors and warnings are reported with an console message
124  * in mergecap.
125  */
126 static void
127 failure_warning_message(const char *msg_format, va_list ap)
128 {
129   fprintf(stderr, "mergecap: ");
130   vfprintf(stderr, msg_format, ap);
131   fprintf(stderr, "\n");
132 }
133
134 static void
135 list_capture_types(void) {
136   int i;
137   struct string_elem *captypes;
138   GSList *list = NULL;
139
140   captypes = g_new(struct string_elem,WTAP_NUM_FILE_TYPES_SUBTYPES);
141
142   fprintf(stderr, "mergecap: The available capture file types for the \"-F\" flag are:\n");
143   for (i = 0; i < WTAP_NUM_FILE_TYPES_SUBTYPES; i++) {
144     if (wtap_dump_can_open(i)) {
145       captypes[i].sstr = wtap_file_type_subtype_short_string(i);
146       captypes[i].lstr = wtap_file_type_subtype_string(i);
147       list = g_slist_insert_sorted(list, &captypes[i], string_compare);
148     }
149   }
150   g_slist_foreach(list, string_elem_print, NULL);
151   g_slist_free(list);
152   g_free(captypes);
153 }
154
155 static void
156 list_idb_merge_modes(void) {
157   int i;
158
159   fprintf(stderr, "mergecap: The available IDB merge modes for the \"-I\" flag are:\n");
160   for (i = 0; i < IDB_MERGE_MODE_MAX; i++) {
161     fprintf(stderr, "    %s\n", merge_idb_merge_mode_to_string(i));
162   }
163 }
164
165 static gboolean
166 merge_callback(merge_event event, int num,
167                const merge_in_file_t in_files[], const guint in_file_count,
168                void *data _U_)
169 {
170   guint i;
171
172   switch (event) {
173
174     case MERGE_EVENT_INPUT_FILES_OPENED:
175       for (i = 0; i < in_file_count; i++) {
176         fprintf(stderr, "mergecap: %s is type %s.\n", in_files[i].filename,
177                 wtap_file_type_subtype_string(wtap_file_type_subtype(in_files[i].wth)));
178       }
179       break;
180
181     case MERGE_EVENT_FRAME_TYPE_SELECTED:
182       /* for this event, num = frame_type */
183       if (num == WTAP_ENCAP_PER_PACKET) {
184         /*
185          * Find out why we had to choose WTAP_ENCAP_PER_PACKET.
186          */
187         int first_frame_type, this_frame_type;
188
189         first_frame_type = wtap_file_encap(in_files[0].wth);
190         for (i = 1; i < in_file_count; i++) {
191           this_frame_type = wtap_file_encap(in_files[i].wth);
192           if (first_frame_type != this_frame_type) {
193             fprintf(stderr, "mergecap: multiple frame encapsulation types detected\n");
194             fprintf(stderr, "          defaulting to WTAP_ENCAP_PER_PACKET\n");
195             fprintf(stderr, "          %s had type %s (%s)\n",
196                     in_files[0].filename,
197                     wtap_encap_string(first_frame_type),
198                     wtap_encap_short_string(first_frame_type));
199             fprintf(stderr, "          %s had type %s (%s)\n",
200                     in_files[i].filename,
201                     wtap_encap_string(this_frame_type),
202                     wtap_encap_short_string(this_frame_type));
203             break;
204           }
205         }
206       }
207       fprintf(stderr, "mergecap: selected frame_type %s (%s)\n",
208               wtap_encap_string(num),
209               wtap_encap_short_string(num));
210       break;
211
212     case MERGE_EVENT_READY_TO_MERGE:
213       fprintf(stderr, "mergecap: ready to merge records\n");
214       break;
215
216     case MERGE_EVENT_RECORD_WAS_READ:
217       /* for this event, num = count */
218       fprintf(stderr, "Record: %d\n", num);
219       break;
220
221     case MERGE_EVENT_DONE:
222       fprintf(stderr, "mergecap: merging complete\n");
223       break;
224   }
225
226   /* false = do not stop merging */
227   return FALSE;
228 }
229
230 int
231 real_main(int argc, char *argv[])
232 {
233   GString            *comp_info_str;
234   GString            *runtime_info_str;
235   char               *appname;
236   char               *init_progfile_dir_error;
237   int                 opt;
238   static const struct option long_options[] = {
239       {"help", no_argument, NULL, 'h'},
240       {"version", no_argument, NULL, 'V'},
241       {0, 0, 0, 0 }
242   };
243   gboolean            do_append          = FALSE;
244   gboolean            verbose            = FALSE;
245   int                 in_file_count      = 0;
246   guint32             snaplen            = 0;
247 #ifdef PCAP_NG_DEFAULT
248   int                 file_type          = WTAP_FILE_TYPE_SUBTYPE_PCAPNG; /* default to pcapng format */
249 #else
250   int                 file_type          = WTAP_FILE_TYPE_SUBTYPE_PCAP; /* default to pcap format */
251 #endif
252   int                 err                = 0;
253   gchar              *err_info           = NULL;
254   int                 err_fileno;
255   guint32             err_framenum;
256   char               *out_filename       = NULL;
257   merge_result        status             = MERGE_OK;
258   idb_merge_mode      mode               = IDB_MERGE_MODE_MAX;
259   merge_progress_callback_t cb;
260
261   cmdarg_err_init(mergecap_cmdarg_err, mergecap_cmdarg_err_cont);
262
263 #ifdef _WIN32
264   create_app_running_mutex();
265 #endif /* _WIN32 */
266
267   /* Get the compile-time version information string */
268   comp_info_str = get_compiled_version_info(NULL, NULL);
269
270   /* Get the run-time version information string */
271   runtime_info_str = get_runtime_version_info(NULL);
272
273   /* Get the application name with version info */
274   appname = g_strdup_printf("mergecap (Wireshark) %s", get_ws_vcs_version_info());
275
276   /* Add it to the information to be reported on a crash. */
277   ws_add_crash_info("%s\n"
278        "\n"
279        "%s"
280        "\n"
281        "%s",
282     appname, comp_info_str->str, runtime_info_str->str);
283   g_string_free(comp_info_str, TRUE);
284   g_string_free(runtime_info_str, TRUE);
285
286   /*
287    * Get credential information for later use.
288    */
289   init_process_policies();
290
291   /*
292    * Attempt to get the pathname of the directory containing the
293    * executable file.
294    */
295   init_progfile_dir_error = init_progfile_dir(argv[0]);
296   if (init_progfile_dir_error != NULL) {
297     fprintf(stderr,
298             "mergecap: Can't get pathname of directory containing the mergecap program: %s.\n",
299             init_progfile_dir_error);
300     g_free(init_progfile_dir_error);
301   }
302
303   init_report_message(failure_warning_message, failure_warning_message,
304                       NULL, NULL, NULL);
305
306   wtap_init(TRUE);
307
308   /* Process the options first */
309   while ((opt = getopt_long(argc, argv, "aF:hI:s:vVw:", long_options, NULL)) != -1) {
310
311     switch (opt) {
312     case 'a':
313       do_append = !do_append;
314       break;
315
316     case 'F':
317       file_type = wtap_short_string_to_file_type_subtype(optarg);
318       if (file_type < 0) {
319         fprintf(stderr, "mergecap: \"%s\" isn't a valid capture file type\n",
320                 optarg);
321         list_capture_types();
322         status = MERGE_ERR_INVALID_OPTION;
323         goto clean_exit;
324       }
325       break;
326
327     case 'h':
328       printf("Mergecap (Wireshark) %s\n"
329              "Merge two or more capture files into one.\n"
330              "See https://www.wireshark.org for more information.\n",
331              get_ws_vcs_version_info());
332       print_usage(stdout);
333       goto clean_exit;
334       break;
335
336     case 'I':
337       mode = merge_string_to_idb_merge_mode(optarg);
338       if (mode == IDB_MERGE_MODE_MAX) {
339         fprintf(stderr, "mergecap: \"%s\" isn't a valid IDB merge mode\n",
340                 optarg);
341         list_idb_merge_modes();
342         status = MERGE_ERR_INVALID_OPTION;
343         goto clean_exit;
344       }
345       break;
346
347     case 's':
348       snaplen = get_nonzero_guint32(optarg, "snapshot length");
349       break;
350
351     case 'v':
352       verbose = TRUE;
353       break;
354
355     case 'V':
356       comp_info_str = get_compiled_version_info(NULL, NULL);
357       runtime_info_str = get_runtime_version_info(NULL);
358       show_version("Mergecap (Wireshark)", comp_info_str, runtime_info_str);
359       g_string_free(comp_info_str, TRUE);
360       g_string_free(runtime_info_str, TRUE);
361       goto clean_exit;
362       break;
363
364     case 'w':
365       out_filename = optarg;
366       break;
367
368     case '?':              /* Bad options if GNU getopt */
369       switch(optopt) {
370       case'F':
371         list_capture_types();
372         break;
373       case'I':
374         list_idb_merge_modes();
375         break;
376       default:
377         print_usage(stderr);
378       }
379       status = MERGE_ERR_INVALID_OPTION;
380       goto clean_exit;
381       break;
382     }
383   }
384
385   cb.callback_func = merge_callback;
386   cb.data = NULL;
387
388   /* check for proper args; at a minimum, must have an output
389    * filename and one input file
390    */
391   in_file_count = argc - optind;
392   if (!out_filename) {
393     fprintf(stderr, "mergecap: an output filename must be set with -w\n");
394     fprintf(stderr, "          run with -h for help\n");
395     status = MERGE_ERR_INVALID_OPTION;
396     goto clean_exit;
397   }
398   if (in_file_count < 1) {
399     fprintf(stderr, "mergecap: No input files were specified\n");
400     return 1;
401   }
402
403   /* setting IDB merge mode must use PCAPNG output */
404   if (mode != IDB_MERGE_MODE_MAX && file_type != WTAP_FILE_TYPE_SUBTYPE_PCAPNG) {
405     fprintf(stderr, "The IDB merge mode can only be used with PCAPNG output format\n");
406     status = MERGE_ERR_INVALID_OPTION;
407     goto clean_exit;
408   }
409
410   /* if they didn't set IDB merge mode, set it to our default */
411   if (mode == IDB_MERGE_MODE_MAX) {
412     mode = IDB_MERGE_MODE_ALL_SAME;
413   }
414
415   /* open the outfile */
416   if (strcmp(out_filename, "-") == 0) {
417     /* merge the files to the standard output */
418     status = merge_files_to_stdout(file_type,
419                                    (const char *const *) &argv[optind],
420                                    in_file_count, do_append, mode, snaplen,
421                                    appname, verbose ? &cb : NULL,
422                                    &err, &err_info, &err_fileno, &err_framenum);
423   } else {
424     /* merge the files to the outfile */
425     status = merge_files(out_filename, file_type,
426                          (const char *const *) &argv[optind], in_file_count,
427                          do_append, mode, snaplen, appname, verbose ? &cb : NULL,
428                          &err, &err_info, &err_fileno, &err_framenum);
429   }
430
431   switch (status) {
432     case MERGE_OK:
433       break;
434
435     case MERGE_USER_ABORTED:
436       /* we don't catch SIGINT/SIGTERM (yet?), so we couldn't have aborted */
437       g_assert(FALSE);
438       break;
439
440     case MERGE_ERR_CANT_OPEN_INFILE:
441       cfile_open_failure_message("mergecap", argv[optind + err_fileno],
442                                  err, err_info);
443       break;
444
445     case MERGE_ERR_CANT_OPEN_OUTFILE:
446       cfile_dump_open_failure_message("mergecap", out_filename, err, file_type);
447       break;
448
449     case MERGE_ERR_CANT_READ_INFILE:
450       cfile_read_failure_message("mergecap", argv[optind + err_fileno],
451                                  err, err_info);
452       break;
453
454     case MERGE_ERR_BAD_PHDR_INTERFACE_ID:
455       cmdarg_err("Record %u of \"%s\" has an interface ID that does not match any IDB in its file.",
456                  err_framenum, argv[optind + err_fileno]);
457       break;
458
459     case MERGE_ERR_CANT_WRITE_OUTFILE:
460        cfile_write_failure_message("mergecap", argv[optind + err_fileno],
461                                    out_filename, err, err_info, err_framenum,
462                                    file_type);
463        break;
464
465     case MERGE_ERR_CANT_CLOSE_OUTFILE:
466         cfile_close_failure_message(out_filename, err);
467         break;
468
469     default:
470       cmdarg_err("Unknown merge_files error %d", status);
471       break;
472   }
473
474 clean_exit:
475   wtap_cleanup();
476   free_progdirs();
477   return (status == MERGE_OK) ? 0 : 2;
478 }
479
480 /*
481  * Editor modelines  -  http://www.wireshark.org/tools/modelines.html
482  *
483  * Local variables:
484  * c-basic-offset: 2
485  * tab-width: 8
486  * indent-tabs-mode: nil
487  * End:
488  *
489  * vi: set shiftwidth=2 tabstop=8 expandtab:
490  * :indentSize=2:tabSize=8:noTabs=true:
491  */