X-Git-Url: http://git.samba.org/?p=metze%2Fwireshark%2Fwip.git;a=blobdiff_plain;f=mergecap.c;h=0eb9bebca2eba1b3395567d23fc0bb159e3be108;hp=bf58298ccaa10f059aac1ae2ef839c929105f65e;hb=d729d8055be976319e522a2a0d9da7d7b988009b;hpb=98024a007f590ed79ce43b89bcf7287ea240b989 diff --git a/mergecap.c b/mergecap.c index bf58298cca..0eb9bebca2 100644 --- a/mergecap.c +++ b/mergecap.c @@ -1,15 +1,29 @@ -/* Combine two dump files, either by appending or by merging by timestamp +/* Combine dump files, either by appending or by merging by timestamp * - * $Id$ + * Wireshark - Network traffic analyzer + * By Gerald Combs + * Copyright 1998 Gerald Combs * - * Written by Scott Renfro based on + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Mergecap written by Scott Renfro based on * editcap by Richard Sharpe and Guy Harris * */ -#ifdef HAVE_CONFIG_H -#include "config.h" -#endif +#include #include #include @@ -20,193 +34,290 @@ #include #endif -#ifdef HAVE_SYS_TIME_H -#include +#ifdef HAVE_GETOPT_H +#include +#endif + +#ifdef HAVE_LIBZ +#include /* to get the libz version number */ #endif #include #include "wtap.h" -#ifdef HAVE_GETOPT_H -#include -#else -#include "wsgetopt.h" +#ifndef HAVE_GETOPT_LONG +#include #endif -#include "svnversion.h" -#include "merge.h" -#include "wsutil/file_util.h" +#include +#include +#include +#include +#include +#include +#include + +#include #ifdef HAVE_FCNTL_H #include #endif -static int -get_natural_int(const char *string, const char *name) +#ifdef _WIN32 +#include +#endif /* _WIN32 */ + +/* + * Show the usage + */ +static void +print_usage(FILE *output) { - int number; - char *p; + fprintf(output, "\n"); + fprintf(output, "Usage: mergecap [options] -w |- [ ...]\n"); + fprintf(output, "\n"); + fprintf(output, "Output:\n"); + fprintf(output, " -a concatenate rather than merge files.\n"); + fprintf(output, " default is to merge based on frame timestamps.\n"); + fprintf(output, " -s truncate packets to bytes of data.\n"); + fprintf(output, " -w |- set the output filename to or '-' for stdout.\n"); + fprintf(output, " -F set the output file type; default is pcapng.\n"); + fprintf(output, " an empty \"-F\" option will list the file types.\n"); + fprintf(output, " -T set the output file encapsulation type;\n"); + fprintf(output, " default is the same as the first input file.\n"); + fprintf(output, " an empty \"-T\" option will list the encapsulation types.\n"); + fprintf(output, "\n"); + fprintf(output, "Miscellaneous:\n"); + fprintf(output, " -h display this help and exit.\n"); + fprintf(output, " -v verbose output.\n"); +} - number = (int) strtol(string, &p, 10); - if (p == string || *p != '\0') { - fprintf(stderr, "mergecap: The specified %s \"%s\" isn't a decimal number\n", - name, string); - exit(1); - } - if (number < 0) { - fprintf(stderr, "mergecap: The specified %s is a negative number\n", - name); - exit(1); - } - if (number > INT_MAX) { - fprintf(stderr, "mergecap: The specified %s is too large (greater than %d)\n", - name, INT_MAX); - exit(1); - } - return number; +/* + * Report an error in command-line arguments. + */ +static void +mergecap_cmdarg_err(const char *fmt, va_list ap) +{ + fprintf(stderr, "mergecap: "); + vfprintf(stderr, fmt, ap); + fprintf(stderr, "\n"); } -static int -get_positive_int(const char *string, const char *name) +/* + * Report additional information for an error in command-line arguments. + */ +static void +mergecap_cmdarg_err_cont(const char *fmt, va_list ap) { - int number; + vfprintf(stderr, fmt, ap); + fprintf(stderr, "\n"); +} - number = get_natural_int(string, name); +struct string_elem { + const char *sstr; /* The short string */ + const char *lstr; /* The long string */ +}; - if (number == 0) { - fprintf(stderr, "mergecap: The specified %s is zero\n", - name); - exit(1); - } +static gint +string_compare(gconstpointer a, gconstpointer b) +{ + return strcmp(((const struct string_elem *)a)->sstr, + ((const struct string_elem *)b)->sstr); +} - return number; +static gint +string_nat_compare(gconstpointer a, gconstpointer b) +{ + return ws_ascii_strnatcmp(((const struct string_elem *)a)->sstr, + ((const struct string_elem *)b)->sstr); } -/* - * Show the usage - */ static void -usage(void) +string_elem_print(gpointer data, gpointer not_used _U_) { - - fprintf(stderr, "Mergecap %s" -#ifdef SVNVERSION - " (" SVNVERSION " from " SVNPATH ")" -#endif - "\n", VERSION); - fprintf(stderr, "Merge two or more capture files into one.\n"); - fprintf(stderr, "See http://www.wireshark.org for more information.\n"); - fprintf(stderr, "\n"); - fprintf(stderr, "Usage: mergecap [options] -w |- ...\n"); - fprintf(stderr, "\n"); - fprintf(stderr, "Output:\n"); - fprintf(stderr, " -a concatenate rather than merge files.\n"); - fprintf(stderr, " default is to merge based on frame timestamps.\n"); - fprintf(stderr, " -s truncate packets to bytes of data.\n"); - fprintf(stderr, " -w |- set the output filename to or '-' for stdout.\n"); - fprintf(stderr, " -F set the output file type; default is libpcap.\n"); - fprintf(stderr, " an empty \"-F\" option will list the file types.\n"); - fprintf(stderr, " -T set the output file encapsulation type;\n"); - fprintf(stderr, " default is the same as the first input file.\n"); - fprintf(stderr, " an empty \"-T\" option will list the encapsulation types.\n"); - fprintf(stderr, "\n"); - fprintf(stderr, "Miscellaneous:\n"); - fprintf(stderr, " -h display this help and exit.\n"); - fprintf(stderr, " -v verbose output.\n"); + fprintf(stderr, " %s - %s\n", ((struct string_elem *)data)->sstr, + ((struct string_elem *)data)->lstr); } -static void list_capture_types(void) { - int i; +static void +list_capture_types(void) { + int i; + struct string_elem *captypes; + GSList *list = NULL; + + captypes = g_new(struct string_elem,WTAP_NUM_FILE_TYPES_SUBTYPES); + + fprintf(stderr, "mergecap: The available capture file types for the \"-F\" flag are:\n"); + for (i = 0; i < WTAP_NUM_FILE_TYPES_SUBTYPES; i++) { + if (wtap_dump_can_open(i)) { + captypes[i].sstr = wtap_file_type_subtype_short_string(i); + captypes[i].lstr = wtap_file_type_subtype_string(i); + list = g_slist_insert_sorted(list, &captypes[i], string_compare); + } + } + g_slist_foreach(list, string_elem_print, NULL); + g_slist_free(list); + g_free(captypes); +} - fprintf(stderr, "mergecap: The available capture file types for \"F\":\n"); - for (i = 0; i < WTAP_NUM_FILE_TYPES; i++) { - if (wtap_dump_can_open(i)) - fprintf(stderr, " %s - %s\n", - wtap_file_type_short_string(i), wtap_file_type_string(i)); +static void +list_encap_types(void) { + int i; + struct string_elem *encaps; + GSList *list = NULL; + + encaps = g_new(struct string_elem,WTAP_NUM_ENCAP_TYPES); + fprintf(stderr, "mergecap: The available encapsulation types for the \"-T\" flag are:\n"); + for (i = 0; i < WTAP_NUM_ENCAP_TYPES; i++) { + encaps[i].sstr = wtap_encap_short_string(i); + if (encaps[i].sstr != NULL) { + encaps[i].lstr = wtap_encap_string(i); + list = g_slist_insert_sorted(list, &encaps[i], string_nat_compare); } + } + g_slist_foreach(list, string_elem_print, NULL); + g_slist_free(list); + g_free(encaps); } -static void list_encap_types(void) { - int i; - const char *string; +static void +get_mergecap_compiled_info(GString *str) +{ + /* LIBZ */ + g_string_append(str, ", "); +#ifdef HAVE_LIBZ + g_string_append(str, "with libz "); +#ifdef ZLIB_VERSION + g_string_append(str, ZLIB_VERSION); +#else /* ZLIB_VERSION */ + g_string_append(str, "(version unknown)"); +#endif /* ZLIB_VERSION */ +#else /* HAVE_LIBZ */ + g_string_append(str, "without libz"); +#endif /* HAVE_LIBZ */ +} - fprintf(stderr, "mergecap: The available encapsulation types for \"T\":\n"); - for (i = 0; i < WTAP_NUM_ENCAP_TYPES; i++) { - string = wtap_encap_short_string(i); - if (string != NULL) - fprintf(stderr, " %s - %s\n", - string, wtap_encap_string(i)); - } +static void +get_mergecap_runtime_info(GString *str) +{ + /* zlib */ +#if defined(HAVE_LIBZ) && !defined(_WIN32) + g_string_append_printf(str, ", with libz %s", zlibVersion()); +#endif } int main(int argc, char *argv[]) { - int opt; - gboolean do_append = FALSE; - gboolean verbose = FALSE; - int in_file_count = 0; - guint snaplen = 0; - int file_type = WTAP_FILE_PCAP; /* default to libpcap format */ - int frame_type = -2; - int out_fd; - merge_in_file_t *in_files = NULL; - int i; - wtap *wth; + GString *comp_info_str; + GString *runtime_info_str; + int opt; +DIAG_OFF(cast-qual) + static const struct option long_options[] = { + {(char *)"help", no_argument, NULL, 'h'}, + {(char *)"version", no_argument, NULL, 'V'}, + {0, 0, 0, 0 } + }; +DIAG_ON(cast-qual) + gboolean do_append = FALSE; + gboolean verbose = FALSE; + int in_file_count = 0; + guint snaplen = 0; +#ifdef PCAP_NG_DEFAULT + int file_type = WTAP_FILE_TYPE_SUBTYPE_PCAPNG; /* default to pcap format */ +#else + int file_type = WTAP_FILE_TYPE_SUBTYPE_PCAP; /* default to pcapng format */ +#endif + int frame_type = -2; + int out_fd; + merge_in_file_t *in_files = NULL, *in_file; + int i; struct wtap_pkthdr *phdr, snap_phdr; - wtap_dumper *pdh; - int open_err, read_err, write_err, close_err; - gchar *err_info; - int err_fileno; - char *out_filename = NULL; - gboolean got_read_error = FALSE, got_write_error = FALSE; - int count; + wtap_dumper *pdh; + int open_err, read_err = 0, write_err, close_err; + gchar *err_info, *write_err_info = NULL; + int err_fileno; + char *out_filename = NULL; + gboolean got_read_error = FALSE, got_write_error = FALSE; + int count; + + cmdarg_err_init(mergecap_cmdarg_err, mergecap_cmdarg_err_cont); + +#ifdef _WIN32 + arg_list_utf_16to8(argc, argv); + create_app_running_mutex(); +#endif /* _WIN32 */ + + /* Get the compile-time version information string */ + comp_info_str = get_compiled_version_info(NULL, get_mergecap_compiled_info); + + /* Get the run-time version information string */ + runtime_info_str = get_runtime_version_info(get_mergecap_runtime_info); + + /* Add it to the information to be reported on a crash. */ + ws_add_crash_info("Mergecap (Wireshark) %s\n" + "\n" + "%s" + "\n" + "%s", + get_ws_vcs_version_info(), comp_info_str->str, runtime_info_str->str); /* Process the options first */ - while ((opt = getopt(argc, argv, "hvas:T:F:w:")) != -1) { + while ((opt = getopt_long(argc, argv, "aF:hs:T:vVw:", long_options, NULL)) != -1) { switch (opt) { - case 'w': - out_filename = optarg; - break; - case 'a': do_append = !do_append; break; - case 'T': - frame_type = wtap_short_string_to_encap(optarg); - if (frame_type < 0) { - fprintf(stderr, "mergecap: \"%s\" isn't a valid encapsulation type\n", - optarg); - list_encap_types(); - exit(1); - } - break; - case 'F': - file_type = wtap_short_string_to_file_type(optarg); + file_type = wtap_short_string_to_file_type_subtype(optarg); if (file_type < 0) { - fprintf(stderr, "mergecap: \"%s\" isn't a valid capture file type\n", - optarg); + fprintf(stderr, "mergecap: \"%s\" isn't a valid capture file type\n", + optarg); list_capture_types(); - exit(1); + exit(1); } break; - case 'v': - verbose = TRUE; + case 'h': + printf("Mergecap (Wireshark) %s\n" + "Merge two or more capture files into one.\n" + "See http://www.wireshark.org for more information.\n", + get_ws_vcs_version_info()); + print_usage(stdout); + exit(0); break; case 's': snaplen = get_positive_int(optarg, "snapshot length"); break; - case 'h': - usage(); + case 'T': + frame_type = wtap_short_string_to_encap(optarg); + if (frame_type < 0) { + fprintf(stderr, "mergecap: \"%s\" isn't a valid encapsulation type\n", + optarg); + list_encap_types(); + exit(1); + } + break; + + case 'v': + verbose = TRUE; + break; + + case 'V': + show_version("Mergecap (Wireshark)", comp_info_str, runtime_info_str); + g_string_free(comp_info_str, TRUE); + g_string_free(runtime_info_str, TRUE); exit(0); break; + case 'w': + out_filename = optarg; + break; + case '?': /* Bad options if GNU getopt */ switch(optopt) { case'F': @@ -216,13 +327,11 @@ main(int argc, char *argv[]) list_encap_types(); break; default: - usage(); + print_usage(stderr); } exit(1); break; - } - } /* check for proper args; at a minimum, must have an output @@ -243,15 +352,10 @@ main(int argc, char *argv[]) if (!merge_open_in_files(in_file_count, &argv[optind], &in_files, &open_err, &err_info, &err_fileno)) { fprintf(stderr, "mergecap: Can't open %s: %s\n", argv[optind + err_fileno], - wtap_strerror(open_err)); - switch (open_err) { - - case WTAP_ERR_UNSUPPORTED: - case WTAP_ERR_UNSUPPORTED_ENCAP: - case WTAP_ERR_BAD_RECORD: + wtap_strerror(open_err)); + if (err_info != NULL) { fprintf(stderr, "(%s)\n", err_info); g_free(err_info); - break; } return 2; } @@ -259,7 +363,7 @@ main(int argc, char *argv[]) if (verbose) { for (i = 0; i < in_file_count; i++) fprintf(stderr, "mergecap: %s is type %s.\n", argv[optind + i], - wtap_file_type_string(wtap_file_type(in_files[i].wth))); + wtap_file_type_subtype_string(wtap_file_type_subtype(in_files[i].wth))); } if (snaplen == 0) { @@ -316,13 +420,78 @@ main(int argc, char *argv[]) out_fd = ws_open(out_filename, O_WRONLY | O_CREAT | O_TRUNC | O_BINARY, 0644); if (out_fd == -1) { fprintf(stderr, "mergecap: Couldn't open output file %s: %s\n", - out_filename, strerror(errno)); + out_filename, g_strerror(errno)); exit(1); } } /* prepare the outfile */ - pdh = wtap_dump_fdopen(out_fd, file_type, frame_type, snaplen, FALSE /* compressed */, &open_err); + if(file_type == WTAP_FILE_TYPE_SUBTYPE_PCAPNG ){ + wtapng_section_t *shb_hdr; + GString *comment_gstr; + wtapng_iface_descriptions_t *idb_inf = NULL, *idb_inf_merge_file; + wtapng_if_descr_t int_data, *file_int_data; + guint itf_count, itf_id = 0; + + shb_hdr = g_new(wtapng_section_t,1); + comment_gstr = g_string_new("File created by merging: \n"); + + for (i = 0; i < in_file_count; i++) { + g_string_append_printf(comment_gstr, "File%d: %s \n",i+1,in_files[i].filename); + } + shb_hdr->section_length = -1; + /* options */ + shb_hdr->opt_comment = comment_gstr->str; /* NULL if not available */ + shb_hdr->shb_hardware = NULL; /* NULL if not available, UTF-8 string containing the description of the hardware used to create this section. */ + shb_hdr->shb_os = NULL; /* NULL if not available, UTF-8 string containing the name of the operating system used to create this section. */ + shb_hdr->shb_user_appl = g_strdup("mergecap"); /* NULL if not available, UTF-8 string containing the name of the application used to create this section. */ + + if (frame_type == WTAP_ENCAP_PER_PACKET) { + /* create fake IDB info */ + idb_inf = g_new(wtapng_iface_descriptions_t,1); + /* TODO make this the number of DIFFERENT encapsulation types + * check that snaplength is the same too? + */ + idb_inf->interface_data = g_array_new(FALSE, FALSE, sizeof(wtapng_if_descr_t)); + + for (i = 0; i < in_file_count; i++) { + idb_inf_merge_file = wtap_file_get_idb_info(in_files[i].wth); + for (itf_count = 0; itf_count < idb_inf_merge_file->interface_data->len; itf_count++) { + /* read the interface data from the in file to our combined interface data */ + file_int_data = &g_array_index (idb_inf_merge_file->interface_data, wtapng_if_descr_t, itf_count); + int_data.wtap_encap = file_int_data->wtap_encap; + int_data.time_units_per_second = file_int_data->time_units_per_second; + int_data.link_type = file_int_data->link_type; + int_data.snap_len = file_int_data->snap_len; + int_data.if_name = g_strdup(file_int_data->if_name); + int_data.opt_comment = NULL; + int_data.if_description = NULL; + int_data.if_speed = 0; + int_data.if_tsresol = 6; + int_data.if_filter_str = NULL; + int_data.bpf_filter_len = 0; + int_data.if_filter_bpf_bytes = NULL; + int_data.if_os = NULL; + int_data.if_fcslen = -1; + int_data.num_stat_entries = 0; /* Number of ISB:s */ + int_data.interface_statistics = NULL; + + g_array_append_val(idb_inf->interface_data, int_data); + } + g_free(idb_inf_merge_file); + + /* Set fake interface Id in per file data */ + in_files[i].interface_id = itf_id; + itf_id += itf_count; + } + } + + pdh = wtap_dump_fdopen_ng(out_fd, file_type, frame_type, snaplen, + FALSE /* compressed */, shb_hdr, idb_inf, &open_err); + g_string_free(comment_gstr, TRUE); + } else { + pdh = wtap_dump_fdopen(out_fd, file_type, frame_type, snaplen, FALSE /* compressed */, &open_err); + } if (pdh == NULL) { merge_close_in_files(in_file_count, in_files); g_free(in_files); @@ -335,42 +504,61 @@ main(int argc, char *argv[]) count = 1; for (;;) { if (do_append) - wth = merge_append_read_packet(in_file_count, in_files, &read_err, - &err_info); + in_file = merge_append_read_packet(in_file_count, in_files, &read_err, + &err_info); else - wth = merge_read_packet(in_file_count, in_files, &read_err, - &err_info); - if (wth == NULL) { - if (read_err != 0) - got_read_error = TRUE; + in_file = merge_read_packet(in_file_count, in_files, &read_err, + &err_info); + if (in_file == NULL) { + /* EOF */ + break; + } + + if (read_err != 0) { + /* I/O error reading from in_file */ + got_read_error = TRUE; break; } if (verbose) - fprintf(stderr, "Record: %u\n", count++); + fprintf(stderr, "Record: %d\n", count++); /* We simply write it, perhaps after truncating it; we could do other * things, like modify it. */ - phdr = wtap_phdr(wth); + phdr = wtap_phdr(in_file->wth); if (snaplen != 0 && phdr->caplen > snaplen) { snap_phdr = *phdr; snap_phdr.caplen = snaplen; phdr = &snap_phdr; } + if ((file_type == WTAP_FILE_TYPE_SUBTYPE_PCAPNG) && (frame_type == WTAP_ENCAP_PER_PACKET)) { + if (phdr->presence_flags & WTAP_HAS_INTERFACE_ID) { + phdr->interface_id += in_file->interface_id; + } else { + phdr->interface_id = in_file->interface_id; + phdr->presence_flags = phdr->presence_flags | WTAP_HAS_INTERFACE_ID; + } + } - if (!wtap_dump(pdh, phdr, wtap_pseudoheader(wth), - wtap_buf_ptr(wth), &write_err)) { + if (!wtap_dump(pdh, phdr, wtap_buf_ptr(in_file->wth), &write_err, &write_err_info)) { got_write_error = TRUE; break; } } merge_close_in_files(in_file_count, in_files); - if (!got_read_error && !got_write_error) { + if (!got_write_error) { if (!wtap_dump_close(pdh, &write_err)) got_write_error = TRUE; - } else - wtap_dump_close(pdh, &close_err); + } else { + /* + * We already got a write error; no need to report another + * write error on close. + * + * Don't overwrite the earlier write error. + */ + (void)wtap_dump_close(pdh, &close_err); + } if (got_read_error) { /* @@ -380,25 +568,85 @@ main(int argc, char *argv[]) if (in_files[i].state == GOT_ERROR) { fprintf(stderr, "mergecap: Error reading %s: %s\n", in_files[i].filename, wtap_strerror(read_err)); - switch (read_err) { - - case WTAP_ERR_UNSUPPORTED: - case WTAP_ERR_UNSUPPORTED_ENCAP: - case WTAP_ERR_BAD_RECORD: + if (err_info != NULL) { fprintf(stderr, "(%s)\n", err_info); g_free(err_info); - break; } } } } if (got_write_error) { - fprintf(stderr, "mergecap: Error writing to outfile: %s\n", - wtap_strerror(write_err)); + switch (write_err) { + + case WTAP_ERR_UNWRITABLE_ENCAP: + /* + * This is a problem with the particular frame we're writing and + * the file type and subtype we're wwriting; note that, and + * report the frame number and file type/subtype. + */ + fprintf(stderr, "mergecap: Frame %u of \"%s\" has a network type that can't be saved in a \"%s\" file.\n", + in_file ? in_file->packet_num : 0, in_file ? in_file->filename : "UNKNOWN", + wtap_file_type_subtype_string(file_type)); + break; + + case WTAP_ERR_PACKET_TOO_LARGE: + /* + * This is a problem with the particular frame we're writing and + * the file type and subtype we're wwriting; note that, and + * report the frame number and file type/subtype. + */ + fprintf(stderr, "mergecap: Frame %u of \"%s\" is too large for a \"%s\" file.\n", + in_file ? in_file->packet_num : 0, in_file ? in_file->filename : "UNKNOWN", + wtap_file_type_subtype_string(file_type)); + break; + + case WTAP_ERR_UNWRITABLE_REC_TYPE: + /* + * This is a problem with the particular record we're writing and + * the file type and subtype we're wwriting; note that, and + * report the record number and file type/subtype. + */ + fprintf(stderr, "mergecap: Record %u of \"%s\" has a record type that can't be saved in a \"%s\" file.\n", + in_file ? in_file->packet_num : 0, in_file ? in_file->filename : "UNKNOWN", + wtap_file_type_subtype_string(file_type)); + break; + + case WTAP_ERR_UNWRITABLE_REC_DATA: + /* + * This is a problem with the particular record we're writing and + * the file type and subtype we're wwriting; note that, and + * report the record number and file type/subtype. + */ + fprintf(stderr, "mergecap: Record %u of \"%s\" has data that can't be saved in a \"%s\" file.\n(%s)\n", + in_file ? in_file->packet_num : 0, in_file ? in_file->filename : "UNKNOWN", + wtap_file_type_subtype_string(file_type), + write_err_info != NULL ? write_err_info : "no information supplied"); + g_free(write_err_info); + break; + + default: + fprintf(stderr, "mergecap: Error writing to outfile: %s\n", + wtap_strerror(write_err)); + break; + } } g_free(in_files); return (!got_read_error && !got_write_error) ? 0 : 2; } + +/* + * Editor modelines - http://www.wireshark.org/tools/modelines.html + * + * Local variables: + * c-basic-offset: 2 + * tab-width: 8 + * indent-tabs-mode: nil + * End: + * + * vi: set shiftwidth=2 tabstop=8 expandtab: + * :indentSize=2:tabSize=8:noTabs=true: + */ +