/* ** Copyright (C) 2001-2014 by Carnegie Mellon University. ** ** @OPENSOURCE_HEADER_START@ ** ** Use of the SILK system and related source code is subject to the terms ** of the following licenses: ** ** GNU Public License (GPL) Rights pursuant to Version 2, June 1991 ** Government Purpose License Rights (GPLR) pursuant to DFARS 252.227.7013 ** ** NO WARRANTY ** ** ANY INFORMATION, MATERIALS, SERVICES, INTELLECTUAL PROPERTY OR OTHER ** PROPERTY OR RIGHTS GRANTED OR PROVIDED BY CARNEGIE MELLON UNIVERSITY ** PURSUANT TO THIS LICENSE (HEREINAFTER THE "DELIVERABLES") ARE ON AN ** "AS-IS" BASIS. CARNEGIE MELLON UNIVERSITY MAKES NO WARRANTIES OF ANY ** KIND, EITHER EXPRESS OR IMPLIED AS TO ANY MATTER INCLUDING, BUT NOT ** LIMITED TO, WARRANTY OF FITNESS FOR A PARTICULAR PURPOSE, ** MERCHANTABILITY, INFORMATIONAL CONTENT, NONINFRINGEMENT, OR ERROR-FREE ** OPERATION. CARNEGIE MELLON UNIVERSITY SHALL NOT BE LIABLE FOR INDIRECT, ** SPECIAL OR CONSEQUENTIAL DAMAGES, SUCH AS LOSS OF PROFITS OR INABILITY ** TO USE SAID INTELLECTUAL PROPERTY, UNDER THIS LICENSE, REGARDLESS OF ** WHETHER SUCH PARTY WAS AWARE OF THE POSSIBILITY OF SUCH DAMAGES. ** LICENSEE AGREES THAT IT WILL NOT MAKE ANY WARRANTY ON BEHALF OF ** CARNEGIE MELLON UNIVERSITY, EXPRESS OR IMPLIED, TO ANY PERSON ** CONCERNING THE APPLICATION OF OR THE RESULTS TO BE OBTAINED WITH THE ** DELIVERABLES UNDER THIS LICENSE. ** ** Licensee hereby agrees to defend, indemnify, and hold harmless Carnegie ** Mellon University, its trustees, officers, employees, and agents from ** all claims or demands made against them (and any related losses, ** expenses, or attorney's fees) arising out of, or relating to Licensee's ** and/or its sub licensees' negligent use or willful misuse of or ** negligent conduct or willful misconduct regarding the Software, ** facilities, or other rights or assistance granted by Carnegie Mellon ** University under this License, including, but not limited to, any ** claims of product liability, personal injury, death, damage to ** property, or violation of any laws or regulations. ** ** Carnegie Mellon University Software Engineering Institute authored ** documents are sponsored by the U.S. Department of Defense under ** Contract FA8721-05-C-0003. Carnegie Mellon University retains ** copyrights in all material produced under this contract. The U.S. ** Government retains a non-exclusive, royalty-free license to publish or ** reproduce these documents, or allow others to do so, for U.S. ** Government purposes only pursuant to the copyright license under the ** contract clause at 252.227.7013. ** ** @OPENSOURCE_HEADER_END@ */ /* ** rwuniqsetup.c ** ** Application setup for rwuniq. See rwuniq.c for a description. */ #include RCSIDENT("$Id$"); #include #include #include #include #include "rwuniq.h" /* TYPEDEFS AND DEFINES */ /* file handle for --help usage message */ #define USAGE_FH stdout /* where to write filenames if --print-file specified */ #define PRINT_FILENAMES_FH stderr /* suffix for distinct fields */ #define DISTINCT_SUFFIX "-Distinct" /* specific ie identifiers */ #define OCTET_DELTA_COUNT SK_FIELD_IDENT_CREATE(0, 1) #define OCTET_TOTAL_COUNT SK_FIELD_IDENT_CREATE(0, 85) #define PACKET_DELTA_COUNT SK_FIELD_IDENT_CREATE(0, 2) #define PACKET_TOTAL_COUNT SK_FIELD_IDENT_CREATE(0, 86) #define SOURCE_IPV4_ADDRESS SK_FIELD_IDENT_CREATE(0, 8) #define DESTINATION_IPV4_ADDRESS SK_FIELD_IDENT_CREATE(0, 12) #define SOURCE_IPV6_ADDRESS SK_FIELD_IDENT_CREATE(0, 27) #define DESTINATION_IPV6_ADDRESS SK_FIELD_IDENT_CREATE(0, 28) #define IP_NEXT_HOP_IPV4_ADDRESS SK_FIELD_IDENT_CREATE(0, 15) #define IP_NEXT_HOP_IPV6_ADDRESS SK_FIELD_IDENT_CREATE(0, 62) #define FLOW_START_MILLISECONDS SK_FIELD_IDENT_CREATE(0, 152) #define FLOW_END_MILLISECONDS SK_FIELD_IDENT_CREATE(0, 153) #define SILK_FLOW_SENSOR SK_FIELD_IDENT_CREATE(IPFIX_CERT_PEN, 31) #define TCP_CONTROL_BITS SK_FIELD_IDENT_CREATE(0, 6) #define INITIAL_TCP_FLAGS SK_FIELD_IDENT_CREATE(IPFIX_CERT_PEN, 14) #define UNION_TCP_FLAGS SK_FIELD_IDENT_CREATE(IPFIX_CERT_PEN, 15) #define REVERSE_TCP_CONTROL_BITS SK_FIELD_IDENT_CREATE(FB_IE_PEN_REVERSE, 6) #define REVERSE_INITIAL_TCP_FLAGS \ SK_FIELD_IDENT_CREATE(IPFIX_CERT_PEN, 14 | FB_IE_VENDOR_BIT_REVERSE) #define REVERSE_UNION_TCP_FLAGS \ SK_FIELD_IDENT_CREATE(IPFIX_CERT_PEN, 15 | FB_IE_VENDOR_BIT_REVERSE) /* type of field being defined */ typedef enum field_type_en { FIELD_TYPE_KEY, FIELD_TYPE_VALUE, FIELD_TYPE_DISTINCT } field_type_t; typedef struct field_alias_st { const char *alias; const char *ie_name; } field_alias_t; /* LOCAL VARIABLES */ /* create aliases for exisitng value fields. the struct contains the * name of the alias and an ID to match in the builtin_values[] * array */ static const struct builtin_value_aliases_st { const char *ba_name; sk_fieldid_t ba_id; } builtin_value_aliases[] = { {"Flows", SK_FIELD_RECORDS}, {NULL, (sk_fieldid_t)0} }; static const field_alias_t field_aliases[] = { {"sPort", "sourceTransportPort"}, {"dPort", "destinationTransportPort"}, {"protocol", "protocolIdentifier"}, {"flags", "tcpControlBits"}, {"sTime", "flowStartMilliseconds"}, {"eTime", "flowEndMilliseconds"}, {"sensor", "silkFlowSensor"}, {"in", "ingressInterface"}, {"out", "egressInterface"}, {"initialFlags", "initialTCPFlags"}, {"sessionFlags", "unionTCPFlags"}, {"attributes", "silkTCPState"}, {"application", "silkAppLabel"}, {NULL, NULL}}; /* key fields used when parsing the user's --fields switch */ static sk_stringmap_t *key_field_map = NULL; /* available aggregate value fields */ static sk_stringmap_t *value_field_map = NULL; /* the text the user entered for the --fields switch */ static const char *fields_arg = NULL; /* the text the user entered for the --values switch */ static const char *values_arg = NULL; /* the information model */ static fbInfoModel_t *infomodel = NULL; /* array of key field idents requested from input stream */ static sk_vector_t *key_fields_vec = NULL; /* array of distinct value field idents requested from input stream */ static sk_vector_t *distinct_fields_vec = NULL; /* name of program to run to page output */ static char *pager; /* where to copy the input to */ static skstream_t *copy_input = NULL; /* temporary directory */ static const char *temp_directory = NULL; /* how to print IP addresses */ static uint32_t ip_format = SKIPADDR_CANONICAL; /* how to print timestamps */ static uint32_t time_flags = SKTIMESTAMP_NOMSEC; /* the floor of the sTime and/or eTime */ static sktime_t time_bin_size = 0; /* Vector of pointers to varlen data */ static sk_vector_t *varlen_data = NULL; /* delimiter between output columns */ static char delimiter = '|'; /* input checker */ static sk_options_ctx_t *optctx = NULL; /* non-zero if we are shutting down due to a signal; controls whether * errors are printed in appTeardown(). */ static int caught_signal = 0; /* timestamp formats: the first of these will be the default */ static const sk_stringmap_entry_t timestamp_names[] = { {"default", 0, NULL, "yyyy/mm/ddThh:mm:ss"}, {"iso", SKTIMESTAMP_ISO, NULL, "yyyy-mm-dd hh:mm:ss"}, {"m/d/y", SKTIMESTAMP_MMDDYYYY, NULL, "mm/dd/yyyy hh:mm:ss"}, {"epoch", SKTIMESTAMP_EPOCH, NULL, "seconds since UNIX epoch; ignores timezone"}, SK_STRINGMAP_SENTINEL }; static const sk_stringmap_entry_t timestamp_zones[] = { {"utc", SKTIMESTAMP_UTC, NULL, "use UTC"}, {"local", SKTIMESTAMP_LOCAL, NULL, "use TZ environment variable or local timezone"}, SK_STRINGMAP_SENTINEL }; #if 0 static const sk_stringmap_entry_t timestamp_misc[] = { {"no-msec", SKTIMESTAMP_NOMSEC, NULL, "truncate milliseconds"}, SK_STRINGMAP_SENTINEL }; #endif static sktime_t getSTimeFn(const rwRec *rwrec, void *ctx); static sktime_t getETimeFn(const rwRec *rwrec, void *ctx); static sktime_t getElapsedFn(const rwRec *rwrec, void *ctx); static uint64_t getBytesFn(const rwRec *rwrec, void *ctx); static uint64_t getPacketsFn(const rwRec *rwrec, void *ctx); static const sk_unique_recfns_t uniq_recfns = { NULL, /* ctx */ getSTimeFn, /* get_stime */ getETimeFn, /* get_etime */ getElapsedFn, /* get_elapsed */ getBytesFn, /* get_bytes */ getPacketsFn, /* get_packets */ }; /* OPTIONS */ typedef enum { OPT_HELP_FIELDS, OPT_FIELDS, OPT_VALUES, OPT_ALL_COUNTS, /* OPT_BYTES...OPT_DIP_DISTINCT must be contiguous and appear in * same order as in builtin_values[] */ OPT_BYTES, OPT_PACKETS, OPT_FLOWS, OPT_STIME, OPT_ETIME, OPT_SIP_DISTINCT, OPT_DIP_DISTINCT, OPT_PRESORTED_INPUT, OPT_SORT_OUTPUT, #if 0 OPT_BIN_TIME, #endif OPT_TIMESTAMP_FORMAT, OPT_INTEGER_SENSORS, OPT_INTEGER_TCP_FLAGS, OPT_NO_TITLES, OPT_NO_COLUMNS, OPT_COLUMN_SEPARATOR, OPT_NO_FINAL_DELIMITER, OPT_DELIMITED, OPT_PRINT_FILENAMES, OPT_COPY_INPUT, OPT_OUTPUT_PATH, OPT_PAGER } appOptionsEnum; static struct option appOptions[] = { {"help-fields", NO_ARG, 0, OPT_HELP_FIELDS}, {"fields", REQUIRED_ARG, 0, OPT_FIELDS}, {"values", REQUIRED_ARG, 0, OPT_VALUES}, {"all-counts", NO_ARG, 0, OPT_ALL_COUNTS}, {"bytes", OPTIONAL_ARG, 0, OPT_BYTES}, {"packets", OPTIONAL_ARG, 0, OPT_PACKETS}, {"flows", OPTIONAL_ARG, 0, OPT_FLOWS}, {"stime", NO_ARG, 0, OPT_STIME}, {"etime", NO_ARG, 0, OPT_ETIME}, {"sip-distinct", OPTIONAL_ARG, 0, OPT_SIP_DISTINCT}, {"dip-distinct", OPTIONAL_ARG, 0, OPT_DIP_DISTINCT}, {"presorted-input", NO_ARG, 0, OPT_PRESORTED_INPUT}, {"sort-output", NO_ARG, 0, OPT_SORT_OUTPUT}, #if 0 {"bin-time", OPTIONAL_ARG, 0, OPT_BIN_TIME}, #endif {"timestamp-format", REQUIRED_ARG, 0, OPT_TIMESTAMP_FORMAT}, {"integer-sensors", NO_ARG, 0, OPT_INTEGER_SENSORS}, {"integer-tcp-flags", NO_ARG, 0, OPT_INTEGER_TCP_FLAGS}, {"no-titles", NO_ARG, 0, OPT_NO_TITLES}, {"no-columns", NO_ARG, 0, OPT_NO_COLUMNS}, {"column-separator", REQUIRED_ARG, 0, OPT_COLUMN_SEPARATOR}, {"no-final-delimiter", NO_ARG, 0, OPT_NO_FINAL_DELIMITER}, {"delimited", OPTIONAL_ARG, 0, OPT_DELIMITED}, {"print-filenames", NO_ARG, 0, OPT_PRINT_FILENAMES}, {"copy-input", REQUIRED_ARG, 0, OPT_COPY_INPUT}, {"output-path", REQUIRED_ARG, 0, OPT_OUTPUT_PATH}, {"pager", REQUIRED_ARG, 0, OPT_PAGER}, {0,0,0,0} /* sentinel entry */ }; static const char *appHelp[] = { "Describe each possible field and value and exit. Def. no", ("Use these fields as the grouping key. Specify fields as a\n" "\tcomma-separated list of names, IDs, and/or ID-ranges"), ("Compute these values for each group. Def. records\n" "\tSpecify values as a comma-separated list of names"), ("Enable the next five switches--count everything. If no\n" "\tcount is specified, flows are counted. Def. No"), ("Sum bytes in each bin; optionally choose to print\n" "\tbins whose total is in given range; range is MIN or MIN-MAX. Def. No"), ("Sum packets in each bin; optionally choose to print\n" "\tbins whose total is in given range; range is MIN or MIN-MAX. Def. No"), ("Count flow records in each bin; optionally choose to print\n" "\tbins whose count is in given range; range is MIN or MIN-MAX. Def. No"), "Print earliest time flow was seen in each bin. Def. No", "Print latest time flow was seen in each bin. Def. No", ("Count distinct sIPs in each bin; optionally choose to\n" "\tprint bins whose count is in range; range is MIN or MIN-MAX. Def. No"), ("Count distinct dIPs in each bin; optionally choose to\n" "\tprint bins whose count is in range; range is MIN or MIN-MAX. Def. No"), ("Assume input has been presorted using\n" "\trwsort invoked with the exact same --fields value. Def. No"), ("Present the output in sorted order. Def. No"), #if 0 ("When using 'sTime' or 'eTime' as a key, adjust time(s) to\n" "\tto appear in N-second bins (floor of time is used). Def. No, "), #endif NULL, /* generated dynamically */ "Print sensor as an integer. Def. Sensor name", "Print TCP Flags as an integer. Def. No", "Do not print column titles. Def. Print titles", "Disable fixed-width columnar output. Def. Columnar", "Use specified character between columns. Def. '|'", "Suppress column delimiter at end of line. Def. No", "Shortcut for --no-columns --no-final-del --column-sep=CHAR", "Print names of input files as they are opened. Def. No", "Copy all input SiLK Flows to given pipe or file. Def. No", "Send output to given file path. Def. stdout", "Program to invoke to page output. Def. $SILK_PAGER or $PAGER", (char *)NULL }; /* LOCAL FUNCTION PROTOTYPES */ static int appOptionsHandler(clientData cData, int opt_index, char *opt_arg); static void appHandleSignal(int sig); static int timestampFormatParse(const char* opt_arg, uint32_t *out_flags); static void timestampFormatUsage(FILE *fh); static void helpFields(FILE *fh); static int createStringmaps(void); static sk_stringmap_t *createFieldStringmap(const fbInfoModel_t *model); static int parseKeyFields(const char *field_string); static int parseValueFields(const char *field_string); static int isFieldDuplicate( const sk_fieldlist_t *flist, sk_fieldid_t fid, const void *fcontext); static int prepareFileForRead(skstream_t *rwios); static void schemaContextDestroy(void *vctx); /* FUNCTION DEFINITIONS */ /* * appUsageLong(); * * Print complete usage information to USAGE_FH. Pass this * function to skOptionsSetUsageCallback(); skOptionsParse() will * call this funciton and then exit the program when the --help * option is given. */ static void appUsageLong(void) { FILE *fh = USAGE_FH; int i; #define USAGE_MSG \ ("--fields=N [SWITCHES] [FILES]\n" \ "\tSummarize SiLK Flow records into user-defined keyed bins specified\n" \ "\twith the --fields switch. For each keyed bin, print byte, packet,\n" \ "\tand/or flow counts and/or the time window when key was active.\n" \ "\tWhen no files are given on command line, flows are read from STDIN.\n") /* Create the string maps for --fields and --values */ createStringmaps(); fprintf(fh, "%s %s", skAppName(), USAGE_MSG); fprintf(fh, "\nSWITCHES:\n"); skOptionsDefaultUsage(fh); for (i = 0; appOptions[i].name; i++) { fprintf(fh, "--%s %s. ", appOptions[i].name, SK_OPTION_HAS_ARG(appOptions[i])); switch ((appOptionsEnum)i) { case OPT_FIELDS: /* Dynamically build the help */ fprintf(fh, "%s\n", appHelp[i]); skStringMapPrintUsage(key_field_map, fh, 4); break; case OPT_VALUES: fprintf(fh, "%s\n", appHelp[i]); skStringMapPrintUsage(value_field_map, fh, 4); break; #if 0 case OPT_BIN_TIME: fprintf(fh, "%s%d\n", appHelp[i], DEFAULT_TIME_BIN); break; #endif case OPT_TIMESTAMP_FORMAT: timestampFormatUsage(fh); skOptionsIPFormatUsage(fh); break; default: /* Simple help text from the appHelp array */ fprintf(fh, "%s\n", appHelp[i]); break; } } skOptionsCtxOptionsUsage(optctx, fh); skOptionsTempDirUsage(fh); sksiteOptionsUsage(fh); } /* * appSetup(argc, argv); * * Perform all the setup for this application include setting up * required modules, parsing options, etc. This function should be * passed the same arguments that were passed into main(). * * Returns to the caller if all setup succeeds. If anything fails, * this function will cause the application to exit with a FAILURE * exit status. */ void appSetup(int argc, char **argv) { SILK_FEATURES_DEFINE_STRUCT(features); int optctx_flags; char *path; int rv; /* verify same number of options and help strings */ assert((sizeof(appHelp)/sizeof(char *)) == (sizeof(appOptions)/sizeof(struct option))); /* register the application */ skAppRegister(argv[0]); skAppVerifyFeatures(&features, NULL); skOptionsSetUsageCallback(&appUsageLong); /* initialize globals */ memset(&app_flags, 0, sizeof(app_flags)); memset(&output, 0, sizeof(output)); output.of_fp = stdout; varlen_data = sk_vector_create(sizeof(void *)); optctx_flags = (SK_OPTIONS_CTX_INPUT_SILK_FLOW | SK_OPTIONS_CTX_ALLOW_STDIN | SK_OPTIONS_CTX_XARGS); /* Prepare the information model */ infomodel = fbInfoModelAlloc(); skipfixCERTAugmentInfoModel(infomodel); /* register the options */ if (skOptionsCtxCreate(&optctx, optctx_flags) || skOptionsCtxOptionsRegister(optctx) || skOptionsRegister(appOptions, &appOptionsHandler, NULL) || skOptionsTempDirRegister(&temp_directory) || skOptionsIPFormatRegister(&ip_format) || sksiteOptionsRegister(SK_SITE_FLAG_CONFIG_FILE)) { skAppPrintErr("Unable to register options"); appExit(EXIT_FAILURE); } /* register the teardown handler */ if (atexit(appTeardown) < 0) { skAppPrintErr("Unable to register appTeardown() with atexit()"); appExit(EXIT_FAILURE); } /* parse options */ rv = skOptionsCtxOptionsParse(optctx, argc, argv); if (rv < 0) { skAppUsage(); /* never returns */ } /* try to load site config file; if it fails, we will not be able * to resolve flowtype and sensor from input file names, but we * should not consider it a complete failure */ sksiteConfigure(0); /* create the ascii format stream and set its properties */ ipfix_fmt = sk_formatter_create(); sk_formatter_set_delimeter(ipfix_fmt, delimiter); if (app_flags.no_final_delimiter) { sk_formatter_set_no_final_delimeter(ipfix_fmt); } if (app_flags.no_columns) { sk_formatter_set_no_columns(ipfix_fmt); } /* set up the key_field_map and value_field_map */ if (createStringmaps()) { appExit(EXIT_FAILURE); } /* make sure the user specified at least one key field */ if (fields_arg == NULL || fields_arg[0] == '\0') { skAppPrintErr("The --%s switch is required", appOptions[OPT_FIELDS].name); skAppUsage(); /* never returns */ } /* parse the --fields and --values switches */ if (parseKeyFields(fields_arg)) { appExit(EXIT_FAILURE); } if (parseValueFields(values_arg)) { appExit(EXIT_FAILURE); } sk_formatter_finalize(ipfix_fmt); /* make certain stdout is not being used for multiple outputs */ if (copy_input && ((0 == strcmp(skStreamGetPathname(copy_input), "-")) || (0 == strcmp(skStreamGetPathname(copy_input), "stdout")))) { if ((NULL == output.of_name) || (0 == strcmp(output.of_name, "-")) || (0 == strcmp(output.of_name, "stdout"))) { skAppPrintErr("May not use stdout for multiple output streams"); exit(EXIT_FAILURE); } } /* create and initialize the uniq object */ if (app_flags.presorted_input) { if (skPresortedUniqueCreate(&ps_uniq)) { appExit(EXIT_FAILURE); } skPresortedUniqueSetTimeBinSize(ps_uniq, time_bin_size); skPresortedUniqueSetTempDirectory(ps_uniq, temp_directory); skPresortedUniqueSetErrorFunction(ps_uniq, skAppPrintErr); if (skPresortedUniqueSetFields(ps_uniq, key_fields, distinct_fields, value_fields)) { skAppPrintErr("Unable to set fields"); appExit(EXIT_FAILURE); } skUniqueSetRecfns(uniq, &uniq_recfns); while ((rv = skOptionsCtxNextArgument(optctx, &path)) == 0) { skPresortedUniqueAddInputFile(ps_uniq, path); } if (rv < 0) { appExit(EXIT_FAILURE); } skPresortedUniqueSetPostOpenFn(ps_uniq, prepareFileForRead); } else { if (skUniqueCreate(&uniq)) { appExit(EXIT_FAILURE); } if (app_flags.sort_output) { skUniqueSetSortedOutput(uniq); } skUniqueSetTimeBinSize(uniq, time_bin_size); skUniqueSetTempDirectory(uniq, temp_directory); skUniqueSetErrorFunction(uniq, skAppPrintErr); if (skUniqueSetFields(uniq, key_fields, distinct_fields, value_fields) || skUniquePrepareForInput(uniq)) { skAppPrintErr("Unable to set fields"); appExit(EXIT_FAILURE); } skUniqueSetRecfns(uniq, &uniq_recfns); } /* open the --output-path. the 'of_name' member is NULL if user * didn't get an output-path. */ if (output.of_name) { rv = skFileptrOpen(&output, SK_IO_WRITE); if (rv) { skAppPrintErr("Unable to open %s '%s': %s", appOptions[OPT_OUTPUT_PATH].name, output.of_name, skFileptrStrerror(rv)); appExit(EXIT_FAILURE); } } /* open the --copy-input destination */ if (copy_input) { rv = skStreamOpen(copy_input); if (rv) { skStreamPrintLastErr(copy_input, rv, &skAppPrintErr); appExit(EXIT_FAILURE); } } /* set signal handler to clean up temp files on SIGINT, SIGTERM, etc */ if (skAppSetSignalHandler(&appHandleSignal)) { appExit(EXIT_FAILURE); } return; /* OK */ } /* * appTeardown() * * Teardown all modules, close all files, and tidy up all * application state. * * This function is idempotent. */ void appTeardown(void) { static int teardownFlag = 0; int rv; if (teardownFlag) { return; } teardownFlag = 1; skUniqueDestroy(&uniq); skPresortedUniqueDestroy(&ps_uniq); /* destroy field lists */ skFieldListDestroy(&key_fields); skFieldListDestroy(&distinct_fields); skFieldListDestroy(&value_fields); /* destroy output */ sk_formatter_destroy(ipfix_fmt); ipfix_fmt = NULL; /* close output */ if (output.of_name) { skFileptrClose(&output, &skAppPrintErr); } /* close the --copy-input */ if (copy_input) { rv = skStreamClose(copy_input); if (rv && rv != SKSTREAM_ERR_NOT_OPEN) { skStreamPrintLastErr(copy_input, rv, &skAppPrintErr); } skStreamDestroy(©_input); } /* Destroy schemas */ sk_schema_destroy(uniq_schema); uniq_schema = NULL; sk_record_destroy(uniq_rec); uniq_rec = NULL; schemaContextDestroy(uniq_ctx); uniq_ctx = NULL; /* Destroy varlen data */ if (varlen_data) { size_t i; void *data; for (i = 0; i < sk_vector_get_count(varlen_data); ++i) { sk_vector_get_value(varlen_data, i, &data); free(data); } sk_vector_destroy(varlen_data); varlen_data = NULL; } sk_vector_destroy(key_fields_vec); sk_vector_destroy(distinct_fields_vec); free(asciibuf); asciibuf = NULL; asciilen = 0; /* destroy string maps for keys and values */ if (key_field_map) { skStringMapDestroy(key_field_map); key_field_map = NULL; } if (value_field_map) { skStringMapDestroy(value_field_map); value_field_map = NULL; } skOptionsCtxDestroy(&optctx); if (infomodel) { fbInfoModelFree(infomodel); infomodel = NULL; } skAppUnregister(); } /* * status = appOptionsHandler(cData, opt_index, opt_arg); * * Called by skOptionsParse(), this handles a user-specified switch * that the application has registered, typically by setting global * variables. Returns 1 if the switch processing failed or 0 if it * succeeded. Returning a non-zero from from the handler causes * skOptionsParse() to return a negative value. * * The clientData in 'cData' is typically ignored; 'opt_index' is * the index number that was specified as the last value for each * struct option in appOptions[]; 'opt_arg' is the user's argument * to the switch for options that have a REQUIRED_ARG or an * OPTIONAL_ARG. */ static int appOptionsHandler( clientData UNUSED(cData), int opt_index, char *opt_arg) { size_t i; int rv; switch ((appOptionsEnum)opt_index) { case OPT_HELP_FIELDS: helpFields(USAGE_FH); exit(EXIT_SUCCESS); case OPT_FIELDS: if (fields_arg) { skAppPrintErr("Invalid %s: Switch used multiple times", appOptions[opt_index].name); return 1; } fields_arg = opt_arg; break; case OPT_VALUES: if (values_arg) { skAppPrintErr("Invalid %s: Switch used multiple times", appOptions[opt_index].name); return 1; } values_arg = opt_arg; break; case OPT_ALL_COUNTS: for (i = 0; i < num_builtin_values; ++i) { if (builtin_values[i].bf_all_counts) { builtin_values[i].bf_switched_on = 1; } } break; case OPT_BYTES: case OPT_PACKETS: case OPT_FLOWS: case OPT_STIME: case OPT_ETIME: case OPT_SIP_DISTINCT: case OPT_DIP_DISTINCT: i = opt_index - OPT_BYTES; builtin_values[i].bf_switched_on = 1; if (opt_arg) { rv = skStringParseRange64(&builtin_values[i].bf_min, &builtin_values[i].bf_max, opt_arg, 0, 0, SKUTILS_RANGE_SINGLE_OPEN); if (rv) { goto PARSE_ERROR; } /* treat a single value as having no max, not as a range * of a single value */ if ((builtin_values[i].bf_min == builtin_values[i].bf_max) && !strchr(opt_arg, '-')) { builtin_values[i].bf_max = UINT64_MAX; } app_flags.check_limits = 1; } break; #if 0 case OPT_BIN_TIME: if (opt_arg == NULL || opt_arg[0] == '\0') { /* no time given; use default */ time_bin_size = sktimeCreate(DEFAULT_TIME_BIN, 0); } else { /* parse user's time */ rv = skStringParseUint32(&val32, opt_arg, 1, 0); if (rv) { goto PARSE_ERROR; } time_bin_size = sktimeCreate(val32, 0); } break; #endif case OPT_PRESORTED_INPUT: app_flags.presorted_input = 1; break; case OPT_SORT_OUTPUT: app_flags.sort_output = 1; break; case OPT_TIMESTAMP_FORMAT: if (timestampFormatParse(opt_arg, &time_flags)) { return 1; } break; case OPT_INTEGER_SENSORS: app_flags.integer_sensors = 1; break; case OPT_INTEGER_TCP_FLAGS: app_flags.integer_tcp_flags = 1; break; case OPT_NO_TITLES: app_flags.no_titles = 1; break; case OPT_NO_COLUMNS: app_flags.no_columns = 1; break; case OPT_NO_FINAL_DELIMITER: app_flags.no_final_delimiter = 1; break; case OPT_COLUMN_SEPARATOR: delimiter = opt_arg[0]; break; case OPT_DELIMITED: app_flags.no_columns = 1; app_flags.no_final_delimiter = 1; if (opt_arg) { delimiter = opt_arg[0]; } break; case OPT_PRINT_FILENAMES: app_flags.print_filenames = 1; break; case OPT_COPY_INPUT: if (copy_input) { skAppPrintErr("Invalid %s: Switch used multiple times", appOptions[opt_index].name); return 1; } if ((rv=skStreamCreate(©_input, SK_IO_WRITE, SK_CONTENT_SILK_FLOW)) || (rv = skStreamBind(copy_input, opt_arg))) { skStreamPrintLastErr(copy_input, rv, &skAppPrintErr); return 1; } break; case OPT_OUTPUT_PATH: if (output.of_name) { skAppPrintErr("Invalid %s: Switch used multiple times", appOptions[opt_index].name); return 1; } output.of_name = opt_arg; break; case OPT_PAGER: pager = opt_arg; break; } return 0; /* OK */ PARSE_ERROR: skAppPrintErr("Invalid %s '%s': %s", appOptions[opt_index].name, opt_arg, skStringParseStrerror(rv)); return 1; } /* * appExit(status) * * Exit the application with the given status. */ void appExit(int status) { appTeardown(); exit(status); } /* * appHandleSignal(signal_value) * * Call appExit() to exit the program. If signal_value is SIGPIPE, * close cleanly; otherwise print a message that we've caught the * signal and exit with EXIT_FAILURE. */ static void appHandleSignal(int sig) { caught_signal = 1; if (sig == SIGPIPE) { /* we get SIGPIPE if something downstream, like rwcut, exits * early, so don't bother to print a warning, and exit * successfully */ appExit(EXIT_SUCCESS); } else { skAppPrintErr("Caught signal..cleaning up and exiting"); appExit(EXIT_FAILURE); } } /* * status = timestampFormatParse(format_string, out_flags); * * Parse the comma-separated list of timestamp format strings * contained in 'format_string' and set 'out_flags' to the result * of parsing the string. Return 0 on success, or -1 if parsing of * the values fails or conflicting values are given. */ static int timestampFormatParse( const char *format, uint32_t *out_flags) { char buf[256]; char *errmsg; sk_stringmap_t *str_map = NULL; sk_stringmap_iter_t *iter = NULL; sk_stringmap_entry_t *found_entry; const sk_stringmap_entry_t *entry; int name_seen = 0; int zone_seen = 0; int rv = -1; /* create a stringmap of the available timestamp formats */ if (SKSTRINGMAP_OK != skStringMapCreate(&str_map)) { skAppPrintOutOfMemory(NULL); goto END; } if (skStringMapAddEntries(str_map, -1, timestamp_names) != SKSTRINGMAP_OK){ skAppPrintOutOfMemory(NULL); goto END; } if (skStringMapAddEntries(str_map, -1, timestamp_zones) != SKSTRINGMAP_OK){ skAppPrintOutOfMemory(NULL); goto END; } /* attempt to match */ if (skStringMapParse(str_map, format, SKSTRINGMAP_DUPES_ERROR, &iter, &errmsg)) { skAppPrintErr("Invalid %s: %s", appOptions[OPT_TIMESTAMP_FORMAT].name, errmsg); goto END; } *out_flags = SKTIMESTAMP_NOMSEC; while (skStringMapIterNext(iter, &found_entry, NULL) == SK_ITERATOR_OK) { *out_flags |= found_entry->id; switch (found_entry->id) { #if 0 case SKTIMESTAMP_NOMSEC: break; #endif case 0: case SKTIMESTAMP_EPOCH: case SKTIMESTAMP_ISO: case SKTIMESTAMP_MMDDYYYY: if (name_seen) { entry = timestamp_names; strncpy(buf, entry->name, sizeof(buf)); for (++entry; entry->name; ++entry) { strncat(buf, ",", sizeof(buf)-strlen(buf)-1); strncat(buf, entry->name, sizeof(buf)-strlen(buf)-1); } skAppPrintErr("Invalid %s: May only specify one of %s", appOptions[OPT_TIMESTAMP_FORMAT].name, buf); goto END; } name_seen = 1; break; case SKTIMESTAMP_UTC: case SKTIMESTAMP_LOCAL: if (zone_seen) { entry = timestamp_zones; strncpy(buf, entry->name, sizeof(buf)); for (++entry; entry->name; ++entry) { strncat(buf, ",", sizeof(buf)-strlen(buf)-1); strncat(buf, entry->name, sizeof(buf)-strlen(buf)-1); } skAppPrintErr("Invalid %s: May only specify one of %s", appOptions[OPT_TIMESTAMP_FORMAT].name, buf); goto END; } zone_seen = 1; break; default: skAbortBadCase(found_entry->id); } } rv = 0; END: if (str_map) { skStringMapDestroy(str_map); } if (iter) { skStringMapIterDestroy(iter); } return rv; } /* * timestampFormatUsage(fh); * * Print the description of the argument to the --timestamp-format * switch to the 'fh' file handle. */ static void timestampFormatUsage(FILE *fh) { const sk_stringmap_entry_t *e; const char *label; fprintf(fh, "Print times in specified format: Def. %s,%s\n", timestamp_names[0].name, timestamp_zones[(SK_ENABLE_LOCALTIME != 0)].name); label = "Format:"; for (e = timestamp_names; e->name; ++e) { fprintf(fh, "\t%-10s%-8s - %s\n", label, e->name, (const char*)e->userdata); label = ""; } label = "Timezone:"; for (e = timestamp_zones; e->name; ++e) { fprintf(fh, "\t%-10s%-8s - %s\n", label, e->name, (const char*)e->userdata); label = ""; } #if 0 label = "Misc:"; for (e = timestamp_misc; e->name; ++e) { fprintf(fh, "\t%-10s%-8s - %s\n", label, e->name, (const char*)e->userdata); label = ""; } #endif /* 0 */ } /* * helpFields(fh); * * Print a description of each field to the 'fh' file pointer */ static void helpFields( FILE *fh) { fprintf(fh, "Currently unsupported.\n"); } /* * value_to_ascii(rwrec, buf, bufsize, field_entry, extra); * * Invoked by rwAsciiPrintRecExtra() to get the value for an * aggregate value field. This function is called for built-in * aggregate values as well as plug-in defined values. * * Fill 'buf' with the value for the column represented by the * aggregate value field list entry 'field_entry'. 'rwrec' is * ignored; 'extra' is an array[3] that contains the buffers for * the key, aggregate value, and distinct field-lists. This * function should write no more than 'bufsize' characters to * 'buf'. */ static int value_to_ascii( const rwRec UNUSED(*rwrec), char *text_buf, size_t text_buf_size, void *v_fl_entry, void *v_outbuf) { sk_fieldentry_t *fl_entry = (sk_fieldentry_t*)v_fl_entry; uint64_t val64; uint32_t val32; size_t len = 0; switch (skFieldListEntryGetId(fl_entry)) { case SK_FIELD_SUM_BYTES: case SK_FIELD_SUM_PACKETS: skFieldListExtractFromBuffer(value_fields, ((uint8_t**)v_outbuf)[1], fl_entry, (uint8_t*)&val64); len = snprintf(text_buf, text_buf_size, ("%" PRIu64), val64); break; case SK_FIELD_RECORDS: case SK_FIELD_SUM_ELAPSED: skFieldListExtractFromBuffer(value_fields, ((uint8_t**)v_outbuf)[1], fl_entry, (uint8_t*)&val32); len = snprintf(text_buf, text_buf_size, ("%" PRIu32), val32); break; case SK_FIELD_MIN_STARTTIME: case SK_FIELD_MAX_ENDTIME: skFieldListExtractFromBuffer(value_fields, ((uint8_t**)v_outbuf)[1], fl_entry, (uint8_t*)&val32); if (text_buf_size <= SKTIMESTAMP_STRLEN) { return SKTIMESTAMP_STRLEN; } len = strlen( sktimestamp_r(text_buf, sktimeCreate(val32, 0), time_flags)); break; case SK_FIELD_CALLER: default: skAbortBadCase(skFieldListEntryGetId(fl_entry)); } return (len > text_buf_size) ? text_buf_size : len; } /* * distinct_to_ascii(rwrec, buf, bufsize, field_entry, extra); * * Invoked by rwAsciiPrintRecExtra() to get the value for a * distinct field. This function is called for built-in distinct * fields as well as those from a plug-in. * * Fill 'buf' with the value for the column represented by the * distinct field list entry 'field_entry'. 'rwrec' is ignored; * 'extra' is an array[3] that contains the buffers for the key, * aggregate value, and distinct field-lists. This function should * write no more than 'bufsize' characters to 'buf'. */ static int distinct_to_ascii( const rwRec UNUSED(*rwrec), char *text_buf, size_t text_buf_size, void *v_fl_entry, void *v_outbuf) { sk_fieldentry_t *fl_entry = (sk_fieldentry_t*)v_fl_entry; size_t len; union value_un { uint8_t ar[HASHLIB_MAX_VALUE_WIDTH]; uint64_t u64; uint32_t u32; uint16_t u16; uint8_t u8; } value; len = skFieldListEntryGetBinOctets(fl_entry); switch (len) { case 1: skFieldListExtractFromBuffer(distinct_fields, ((uint8_t**)v_outbuf)[2], fl_entry, &value.u8); len = snprintf(text_buf, text_buf_size, ("%" PRIu8), value.u8); break; case 2: skFieldListExtractFromBuffer(distinct_fields, ((uint8_t**)v_outbuf)[2], fl_entry, (uint8_t*)&value.u16); len = snprintf(text_buf, text_buf_size, ("%" PRIu16), value.u16); break; case 4: skFieldListExtractFromBuffer(distinct_fields, ((uint8_t**)v_outbuf)[2], fl_entry, (uint8_t*)&value.u32); len = snprintf(text_buf, text_buf_size, ("%" PRIu32), value.u32); break; case 8: skFieldListExtractFromBuffer(distinct_fields, ((uint8_t**)v_outbuf)[2], fl_entry, (uint8_t*)&value.u64); len = snprintf(text_buf, text_buf_size, ("%" PRIu64), value.u64); break; case 3: case 5: case 6: case 7: value.u64 = 0; #if SK_BIG_ENDIAN skFieldListExtractFromBuffer(distinct_fields, ((uint8_t**)v_outbuf)[2], fl_entry, &value.ar[8 - len]); #else skFieldListExtractFromBuffer(distinct_fields, ((uint8_t**)v_outbuf)[2], fl_entry, &value.ar[0]); #endif /* #else of #if SK_BIG_ENDIAN */ len = snprintf(text_buf, text_buf_size, ("%" PRIu64), value.u64); break; default: skFieldListExtractFromBuffer(distinct_fields, ((uint8_t**)v_outbuf)[2], fl_entry, value.ar); len = snprintf(text_buf, text_buf_size, ("%" PRIu64), value.u64); break; } return (len > text_buf_size) ? text_buf_size : len; } /** Comparison functions for numbers **/ #ifdef SK_HAVE_ALIGNED_ACCESS_REQUIRED #define IMPLEMENT_NUM_COMPARE(_typ_) \ static int \ field_ ## _typ_ ## _compare( \ const uint8_t *a, \ const uint8_t *b, \ void *UNUSED(ctx)) \ { \ _typ_ av; \ _typ_ bv; \ memcpy(&av, a, sizeof(_typ_)); \ memcpy(&bv, b, sizeof(_typ_)); \ return (av < bv) ? -1 : ((av > bv) ? 1 : 0); \ } #else #define IMPLEMENT_NUM_COMPARE(_typ_) \ static int \ field_ ## _typ_ ## _compare( \ const uint8_t *a, \ const uint8_t *b, \ void *UNUSED(ctx)) \ { \ const _typ_ *av = (_typ_ *)a; \ const _typ_ *bv = (_typ_ *)b; \ return (*av < *bv) ? -1 : ((*av > *bv) ? 1 : 0); \ } #endif IMPLEMENT_NUM_COMPARE(uint8_t) IMPLEMENT_NUM_COMPARE(uint16_t) IMPLEMENT_NUM_COMPARE(uint32_t) IMPLEMENT_NUM_COMPARE(uint64_t) IMPLEMENT_NUM_COMPARE(int8_t) IMPLEMENT_NUM_COMPARE(int16_t) IMPLEMENT_NUM_COMPARE(int32_t) IMPLEMENT_NUM_COMPARE(int64_t) IMPLEMENT_NUM_COMPARE(float) IMPLEMENT_NUM_COMPARE(double) /** Comparison functions for string-likes **/ #define IMPLEMENT_MEM_COMPARE(_name_, _cmp_) \ static int \ field_ ## _name_ ## _compare( \ const uint8_t *a, \ const uint8_t *b, \ void *UNUSED(ctx)) \ { \ size_t len; \ fbVarfield_t av; \ fbVarfield_t bv; \ memcpy(&av, a, sizeof(const fbVarfield_t)); \ memcpy(&bv, b, sizeof(const fbVarfield_t)); \ len = (av.len < bv.len) ? av.len : bv.len; \ return _cmp_((const char *)av.buf, (const char *)bv.buf, len); \ } IMPLEMENT_MEM_COMPARE(octet, memcmp) IMPLEMENT_MEM_COMPARE(string, strncmp) static void field_rec_to_bin( const rwRec *rec, uint8_t *dest, void *ctx); /* fieldlist entrydata members for each IPFIX type */ static sk_fieldlist_entrydata_t entrydata[FB_IP6_ADDR + 1] = { {field_rec_to_bin, field_octet_compare, NULL, NULL, NULL, NULL, sizeof(fbVarfield_t)}, {field_rec_to_bin, field_uint8_t_compare, NULL, NULL, NULL, NULL, 1}, {field_rec_to_bin, field_uint16_t_compare, NULL, NULL, NULL, NULL, 2}, {field_rec_to_bin, field_uint32_t_compare, NULL, NULL, NULL, NULL, 4}, {field_rec_to_bin, field_uint64_t_compare, NULL, NULL, NULL, NULL, 8}, {field_rec_to_bin, field_int8_t_compare, NULL, NULL, NULL, NULL, 1}, {field_rec_to_bin, field_int16_t_compare, NULL, NULL, NULL, NULL, 2}, {field_rec_to_bin, field_int32_t_compare, NULL, NULL, NULL, NULL, 4}, {field_rec_to_bin, field_int64_t_compare, NULL, NULL, NULL, NULL, 8}, {field_rec_to_bin, field_float_compare, NULL, NULL, NULL, NULL, sizeof(float)}, {field_rec_to_bin, field_double_compare, NULL, NULL, NULL, NULL, sizeof(double)}, {field_rec_to_bin, NULL, NULL, NULL, NULL, NULL, 1}, {field_rec_to_bin, NULL, NULL, NULL, NULL, NULL, 6}, {field_rec_to_bin, field_string_compare, NULL, NULL, NULL, NULL, sizeof(fbVarfield_t)}, {field_rec_to_bin, field_uint64_t_compare, NULL, NULL, NULL, NULL, sizeof(sktime_t)}, {field_rec_to_bin, field_uint64_t_compare, NULL, NULL, NULL, NULL, sizeof(sktime_t)}, {field_rec_to_bin, field_uint64_t_compare, NULL, NULL, NULL, NULL, sizeof(sktime_t)}, {field_rec_to_bin, field_uint64_t_compare, NULL, NULL, NULL, NULL, sizeof(sktime_t)}, {field_rec_to_bin, field_uint32_t_compare, NULL, NULL, NULL, NULL, 4}, {field_rec_to_bin, NULL, NULL, NULL, NULL, NULL, 16} }; #define IPADDR_REC_TO_BIN(_name_) \ static void \ _name_ ## _rec_to_bin( \ const rwRec *rec, \ uint8_t *dest, \ void *UNUSED(x)) \ { \ skipaddr_t addr; \ schema_context_t *ctx = (schema_context_t *)sk_schema_get_context( \ sk_record_get_schema(rec)); \ const sk_field_t *v4 = ctx->_name_ ## v4; \ const sk_field_t *v6 = ctx->_name_ ## v6; \ if (v4 == NULL && v6 == NULL) { \ memset(dest, 0, 16); \ return; \ } \ if (v6 != NULL) { \ sk_record_get_ipv6_addr(rec, v6, dest); \ if (!SK_IPV6_IS_ZERO(dest) || v4 == NULL) { \ return; \ } \ } \ sk_record_get_ip_address(rec, v4, &addr); \ skipaddrV4toV6(&addr, &addr); \ skipaddrGetV6(&addr, dest); \ return; \ } IPADDR_REC_TO_BIN(sip) IPADDR_REC_TO_BIN(dip) IPADDR_REC_TO_BIN(nhip) static const struct builtin_fields_st { const char *name; sk_fieldlist_entrydata_t entrydata; } builtin_fields[] = { {"sIP", {sip_rec_to_bin, NULL, NULL, NULL, NULL, NULL, 16}}, {"dIP", {dip_rec_to_bin, NULL, NULL, NULL, NULL, NULL, 16}}, {"nhip", {nhip_rec_to_bin, NULL, NULL, NULL, NULL, NULL, 16}}, {NULL, {NULL, NULL, NULL, NULL, NULL, NULL, 0}} }; static void field_rec_to_bin( const rwRec *rec, uint8_t *dest, void *x) { const sk_field_t *f; schema_context_t *ctx = (schema_context_t *)sk_schema_get_context( sk_record_get_schema(rec)); uintptr_t idx = (uintptr_t)x; uint8_t typ; sk_vector_get_value(ctx->key_fields, idx, &f); if (f == NULL) { sk_vector_get_value(uniq_ctx->key_fields, idx, &f); typ = sk_field_get_type(f); memset(dest, 0, entrydata[typ].bin_octets); } typ = sk_field_get_type(f); switch (typ) { case FB_UINT_8: case FB_UINT_16: case FB_UINT_32: case FB_UINT_64: sk_record_get_sized_uint(rec, f, dest, entrydata[typ].bin_octets); break; case FB_INT_8: case FB_INT_16: case FB_INT_32: case FB_INT_64: sk_record_get_sized_int(rec, f, dest, entrydata[typ].bin_octets); break; case FB_BOOL: { int b; sk_record_get_boolean(rec, f, &b); *dest = b; } break; case FB_FLOAT_32: { float fv; sk_record_get_float32(rec, f, &fv); memcpy(dest, &fv, sizeof(float)); } break; case FB_FLOAT_64: { double dv; sk_record_get_float64(rec, f, &dv); memcpy(dest, &dv, sizeof(double)); } break; case FB_IP4_ADDR: { uint32_t ip4; sk_record_get_ipv4_addr(rec, f, &ip4); memcpy(dest, &ip4, 4); } break; case FB_IP6_ADDR: sk_record_get_ipv6_addr(rec, f, dest); break; case FB_MAC_ADDR: sk_record_get_mac_address(rec, f, dest); break; case FB_DT_SEC: case FB_DT_MILSEC: case FB_DT_MICROSEC: case FB_DT_NANOSEC: { sktime_t t; sk_record_get_datetime(rec, f, &t); memcpy(dest, &t, sizeof(sktime_t)); } break; case FB_STRING: case FB_OCTET_ARRAY: { uint16_t len; fbVarfield_t vf; sk_record_get_value_length(rec, f, &len); vf.buf = (uint8_t *)sk_alloc_bytes(len, SK_ALLOC_FLAG_NO_CLEAR); vf.len = len; sk_record_get_octets(rec, f, vf.buf, &len); sk_vector_append_value(varlen_data, &vf.buf); memcpy(dest, &vf, sizeof(fbVarfield_t)); } break; default: skAbortBadCase(typ); } } static sk_stringmap_t * createFieldStringmap( const fbInfoModel_t *model) { char buf[32]; sk_stringmap_t *map = NULL; fbInfoModelIter_t iter; const fbInfoElement_t *ie; sk_stringmap_id_t id; sk_stringmap_status_t err; sk_stringmap_entry_t entry; const field_alias_t *alias; if ((err = skStringMapCreate(&map))) { goto ERR; } /* Add IEs from the infomodel */ fbInfoModelIterInit(&iter, model); for (id = 0; (ie = fbInfoModelIterNext(&iter)) != NULL; ++id) { if (ie->type > FB_IP6_ADDR) { /* Skip types we don't know how to use (list elements) */ continue; } entry.name = ie->ref.name; entry.id = id; entry.description = ie->description; entry.userdata = ie; if ((err = skStringMapAddEntries(map, 1, &entry))) { goto ERR; } if (ie->ent) { snprintf(buf, sizeof(buf), "ie%" PRIu32 "/%" PRIu16, ie->ent, ie->num); } else { snprintf(buf, sizeof(buf), "ie%" PRIu16, ie->num); } entry.name = buf; if ((err = skStringMapAddEntries(map, 1, &entry))) { goto ERR; } } /* Add simple aliases */ for (alias = field_aliases; alias->alias; ++alias) { sk_stringmap_entry_t *e; if ((err = skStringMapGetByName(map, alias->ie_name, &e))) { goto ERR; } entry = *e; entry.name = alias->alias; if ((err = skStringMapAddEntries(map, 1, &entry))) { goto ERR; } } return map; ERR: skAppPrintErr("Could not create field string map: %s", skStringMapStrerror(err)); skStringMapDestroy(map); return NULL; } /* * ok = createStringmaps(); * * Create the string-maps to assist in parsing the --fields and * --values switches. */ static int createStringmaps(void) { sk_stringmap_status_t sm_err; sk_stringmap_entry_t sm_entry; uint32_t max_id; size_t i; size_t j; key_field_map = createFieldStringmap(infomodel); if (key_field_map == NULL) { return -1; } max_id = 0; /* create the string-map for value field identifiers */ if (skStringMapCreate(&value_field_map)) { skAppPrintErr("Unable to create map for values"); return -1; } /* add the built-in names */ for (i = 0; i < num_builtin_values; ++i) { memset(&sm_entry, 0, sizeof(sk_stringmap_entry_t)); sm_entry.name = builtin_values[i].bf_title; sm_entry.id = i; sm_entry.description = builtin_values[i].bf_description; sm_err = skStringMapAddEntries(value_field_map, 1, &sm_entry); if (sm_err) { skAppPrintErr("Unable to add value field named '%s': %s", sm_entry.name, skStringMapStrerror(sm_err)); return -1; } if (sm_entry.id > max_id) { max_id = sm_entry.id; } } /* add aliases for built-in fields */ for (j = 0; builtin_value_aliases[j].ba_name; ++j) { for (i = 0; i < num_builtin_values; ++i) { if (builtin_value_aliases[j].ba_id == builtin_values[i].bf_id) { memset(&sm_entry, 0, sizeof(sk_stringmap_entry_t)); sm_entry.name = builtin_value_aliases[j].ba_name; sm_entry.id = i; sm_err = skStringMapAddEntries(value_field_map, 1, &sm_entry); if (sm_err) { skAppPrintErr("Unable to add value field named '%s': %s", sm_entry.name, skStringMapStrerror(sm_err)); return -1; } break; } } if (i == num_builtin_values) { skAppPrintErr("No field found with id %d", builtin_value_aliases[j].ba_id); return -1; } } return 0; } /* * status = parseKeyFields(field_string); * * Parse the string that represents the key fields the user wishes * to bin by, create and fill in the global sk_fieldlist_t * 'key_fields', and add columns to the sk_formatter_t. Return 0 * on success or non-zero on error. */ static int parseKeyFields( const char *field_string) { sk_stringmap_iter_t *sm_iter = NULL; sk_stringmap_entry_t *sm_entry = NULL; /* return value; assume failure */ int rv = -1; /* error message generated when parsing fields */ char *errmsg; /* parse the --fields argument */ if (skStringMapParse(key_field_map, field_string, SKSTRINGMAP_DUPES_ERROR, &sm_iter, &errmsg)) { skAppPrintErr("Invalid %s: %s", appOptions[OPT_FIELDS].name, errmsg); goto END; } /* create the field-list */ if (skFieldListCreate(&key_fields)) { skAppPrintErr("Unable to create key field list"); goto END; } /* Create the field vectors */ assert(key_fields_vec == NULL); key_fields_vec = sk_vector_create(sizeof(sk_field_ident_t)); assert(distinct_fields_vec == NULL); distinct_fields_vec = sk_vector_create(sizeof(sk_field_ident_t)); assert(uniq_schema == NULL); sk_schema_create(&uniq_schema, infomodel, NULL, 0); assert(uniq_ctx == NULL); uniq_ctx = sk_alloc(schema_context_t); uniq_ctx->key_fields = sk_vector_create(sizeof(const sk_field_t *)); /* add the key fields to the field-list and to the ascii stream. */ while (skStringMapIterNext(sm_iter, &sm_entry, NULL) == SK_ITERATOR_OK) { const sk_fieldlist_entrydata_t *fl_entry; const fbInfoElement_t *ie; sk_field_t *f; sk_field_ident_t id; sk_formatter_field_t *fmt; uintptr_t idx; /* Get the IE for this entry */ ie = (const fbInfoElement_t *)sm_entry->userdata; assert(ie->type <= FB_IP6_ADDR); /* Add to the field list */ fl_entry = &entrydata[ie->type]; idx = sk_vector_get_count(key_fields_vec); skFieldListAddField(key_fields, fl_entry, (void *)idx); /* Add to the key fields vector and uniq schema */ id = SK_FIELD_IDENT_CREATE(ie->ent, ie->num); sk_vector_append_value(key_fields_vec, &id); sk_schema_insert_field_by_ident(&f, uniq_schema, id, NULL, NULL); sk_vector_append_value(uniq_ctx->key_fields, &f); /* Add to the formatter */ fmt = sk_formatter_add_ie(ipfix_fmt, ie); sk_formatter_field_set_title(ipfix_fmt, fmt, sm_entry->name); /* Set defaults */ sk_formatter_field_set_timestamp_format(ipfix_fmt, fmt, time_flags); sk_formatter_field_set_ipaddr_format(ipfix_fmt, fmt, (skipaddr_flags_t)ip_format); /* Set integer flags for specific fields */ if (app_flags.integer_sensors && id == SILK_FLOW_SENSOR) { sk_formatter_field_set_number_format(ipfix_fmt, fmt, 10); } else if (app_flags.integer_tcp_flags) { switch (id) { case TCP_CONTROL_BITS: case INITIAL_TCP_FLAGS: case UNION_TCP_FLAGS: case REVERSE_TCP_CONTROL_BITS: case REVERSE_INITIAL_TCP_FLAGS: case REVERSE_UNION_TCP_FLAGS: sk_formatter_field_set_number_format(ipfix_fmt, fmt, 10); break; default: break; } } } /* successful */ rv = 0; END: if (rv != 0) { /* something went wrong. clean up */ if (key_fields) { skFieldListDestroy(&key_fields); key_fields = NULL; } } /* do standard clean-up */ if (sm_iter != NULL) { skStringMapIterDestroy(sm_iter); } return rv; } /* * ok = parseValueFields(field_string); * * Parse the string that represents the aggregate value and * distinct fields the user wishes to compute, create and fill in * the global sk_fieldlist_t 'value_fields' and 'distinct_fields', * and add columns to the rwAsciiStream. Return 0 on success or * non-zero on error. * * Returns 0 on success, or non-zero on error. */ static int parseValueFields( const char *value_string) { char strbuf[1024]; sk_stringmap_iter_t *sm_iter = NULL; sk_stringmap_entry_t *sm_entry; sk_stringmap_status_t sm_err; const char *sm_attr; size_t width; size_t i; /* to create a new --values switch */ char *buf = NULL; size_t buf_size; /* return value; assume failure */ int rv = -1; /* error message generated when parsing fields */ char *errmsg; builtin_field_t *bf; sk_fieldentry_t *fl_entry; sk_fieldlist_entrydata_t *fl_data; const fbInfoElement_t *ie; sk_formatter_field_t *fmtfield; sk_field_ident_t id; sk_field_t *f; uintptr_t idx; if (ipv6_policy >= SK_IPV6POLICY_MIX) { /* change the field id of the distinct fields */ for (i = 0, bf = builtin_values; i < num_builtin_values; ++i, ++bf) { switch (bf->bf_id) { case SK_FIELD_SIPv4: bf->bf_id = SK_FIELD_SIPv6; break; case SK_FIELD_DIPv4: bf->bf_id = SK_FIELD_DIPv6; break; default: break; } } } if (time_flags & SKTIMESTAMP_EPOCH) { /* Reduce width of the textual columns for the MIN_STARTTIME * and MAX_ENDTIME fields. */ for (i = 0, bf = builtin_values; i < num_builtin_values; ++i, ++bf) { if ((bf->bf_id == SK_FIELD_MIN_STARTTIME) || (bf->bf_id == SK_FIELD_MAX_ENDTIME)) { bf->bf_text_len = 10; } } } /* * Handling the old style --bytes,--packets,etc switches and * the new --values switch is a bit of a pain. * * First, parse --values if it is provided. If any --values * fields are also specified as stand-alone switches (e.g., * --bytes), turn off the stand-alone switch. * * If any stand-alone switch is still on, create a new --values * switch that includes the names of the stand-alone switches. * Or, if no --values and no stand-alone switches are given, * fall-back to the default and count flow records. */ /* parse the --values field list if given */ if (value_string) { if (skStringMapParseWithAttributes(value_field_map, value_string, SKSTRINGMAP_DUPES_KEEP, &sm_iter, &errmsg)) { skAppPrintErr("Invalid %s: %s", appOptions[OPT_VALUES].name, errmsg); goto END; } /* turn off the --bytes,--packets,etc switches if they also appear * in the --values switch */ while (skStringMapIterNext(sm_iter, &sm_entry, NULL)==SK_ITERATOR_OK) { if (sm_entry->id < num_builtin_values) { builtin_values[sm_entry->id].bf_switched_on = 0; } } skStringMapIterDestroy(sm_iter); sm_iter = NULL; } /* determine whether there are any active --bytes,--packets,etc * switches */ buf_size = 0; for (i = 0, bf = builtin_values; i < num_builtin_values; ++i, ++bf) { if (bf->bf_switched_on) { buf_size += 2 + strlen(bf->bf_title); } } if (buf_size) { /* switches are active; create new --values switch */ if (NULL == value_string) { buf = (char*)malloc(buf_size); if (!buf) { skAppPrintOutOfMemory(NULL); goto END; } buf[0] = '\0'; } else { buf_size += 1 + strlen(value_string); buf = (char*)malloc(buf_size); if (!buf) { skAppPrintOutOfMemory(NULL); goto END; } strncpy(buf, value_string, buf_size); } for (i = 0, bf = builtin_values; i < num_builtin_values; ++i, ++bf) { if (bf->bf_switched_on) { strncat(buf, ",", 2); strncat(buf, bf->bf_title, buf_size - 1 - strlen(buf)); } } value_string = buf; } else if (!value_string) { /* no --values switch and no --bytes,--packets,etc switches, * so count flow records */ for (i = 0, bf = builtin_values; i < num_builtin_values; ++i, ++bf) { if (SK_FIELD_RECORDS == bf->bf_id) { value_string = bf->bf_title; break; } } } /* parse the --values field list */ if (skStringMapParseWithAttributes(value_field_map, value_string, SKSTRINGMAP_DUPES_KEEP, &sm_iter, &errmsg)) { skAppPrintErr("Invalid %s: %s", appOptions[OPT_VALUES].name, errmsg); goto END; } /* create the field-lists */ if (skFieldListCreate(&value_fields)) { skAppPrintErr("Unable to create value field list"); goto END; } if (skFieldListCreate(&distinct_fields)) { skAppPrintErr("Unable to create distinct field list"); goto END; } /* loop over the selected values */ while (skStringMapIterNext(sm_iter, &sm_entry, &sm_attr) == SK_ITERATOR_OK) { assert(sm_entry->id < num_builtin_values); bf = &builtin_values[sm_entry->id]; if (0 == bf->bf_is_distinct) { /* this is a built-in values field; must have no attribute */ if (sm_attr[0]) { skAppPrintErr("Invalid %s: Unrecognized field '%s:%s'", appOptions[OPT_VALUES].name, bf->bf_title, sm_attr); goto END; } if (isFieldDuplicate(value_fields, bf->bf_id, NULL)) { skAppPrintErr("Invalid %s: Duplicate name '%s'", appOptions[OPT_VALUES].name, bf->bf_title); goto END; } fl_entry = skFieldListAddKnownField(value_fields, bf->bf_id, bf); if (NULL == fl_entry) { skAppPrintErr("Cannot add value field '%s' to field list", sm_entry->name); goto END; } if (!(fmtfield = sk_formatter_add_extra_field( ipfix_fmt, &value_to_ascii, fl_entry, bf->bf_text_len))) { skAppPrintErr("Cannot add value field '%s' to stream", sm_entry->name); goto END; } sk_formatter_field_set_title(ipfix_fmt, fmtfield, bf->bf_title); } else if (SK_FIELD_CALLER != bf->bf_id) { /* one of the old sip-distinct,dip-distinct fields; must * have no attribute */ if (sm_attr[0]) { skAppPrintErr("Invalid %s: Unrecognized field '%s:%s'", appOptions[OPT_VALUES].name, bf->bf_title, sm_attr); goto END; } /* is this a duplicate field? */ if (isFieldDuplicate(distinct_fields, bf->bf_id, NULL)) { skAppPrintErr("Invalid %s: Duplicate name '%s'", appOptions[OPT_VALUES].name, bf->bf_title); goto END; } fl_entry = skFieldListAddKnownField(distinct_fields, bf->bf_id,bf); if (NULL == fl_entry) { skAppPrintErr("Cannot add distinct field '%s' to field list", sm_entry->name); goto END; } /* FIXME: Unfinished */ #if 0 if (rwAsciiAppendCallbackFieldExtra(ascii_str, &builtin_distinct_get_title, &distinct_to_ascii, fl_entry, bf->bf_text_len)) { skAppPrintErr("Cannot add distinct field '%s' to stream", sm_entry->name); goto END; } #endif } else { /* got a distinct:KEY field */ if (!sm_attr[0]) { skAppPrintErr(("Invalid %s:" " The distinct value requires a field"), appOptions[OPT_VALUES].name); goto END; } /* need to parse KEY as a key field */ sm_err = skStringMapGetByName(key_field_map, sm_attr, &sm_entry); if (sm_err) { if (strchr(sm_attr, ',')) { skAppPrintErr(("Invalid %s:" " May only distinct over a single field"), appOptions[OPT_VALUES].name); } else { skAppPrintErr("Invalid %s: Bad distinct field '%s': %s", appOptions[OPT_VALUES].name, sm_attr, skStringMapStrerror(sm_err)); } goto END; } assert(sm_entry->userdata); /* distinct:KEY */ if (isFieldDuplicate(distinct_fields, SK_FIELD_CALLER, sm_entry->userdata)) { skAppPrintErr("Invalid %s: Duplicate distinct '%s'", appOptions[OPT_VALUES].name, sm_entry->name); goto END; } ie = (const fbInfoElement_t *)sm_entry->userdata; assert(ie->type <= FB_IP6_ADDR); fl_data = &entrydata[ie->type]; idx = sk_vector_get_count(key_fields_vec); fl_entry = skFieldListAddField( distinct_fields, fl_data, (void *)idx); id = SK_FIELD_IDENT_CREATE(ie->ent, ie->num); sk_vector_append_value(key_fields_vec, &id); sk_schema_insert_field_by_ident(&f, uniq_schema, id, NULL, NULL); sk_vector_append_value(uniq_ctx->key_fields, &f); switch (ie->len) { case 1: width = 3; break; case 2: width = 5; break; case 3: case 4: width = 10; break; default: width = 20; break; } if (!(fmtfield = sk_formatter_add_extra_field( ipfix_fmt, distinct_to_ascii, fl_entry, width))) { skAppPrintErr("Cannot add distinct field '%s' from plugin", sm_entry->name); goto END; } snprintf(strbuf, sizeof(buf), "%s%s", ie->ref.name, DISTINCT_SUFFIX); strbuf[sizeof(buf) - 1] = '\0'; sk_formatter_field_set_title(ipfix_fmt, fmtfield, strbuf); } } sk_schema_freeze(uniq_schema); sk_record_create(&uniq_rec, uniq_schema); rv = 0; END: /* do standard clean-up */ if (sm_iter) { skStringMapIterDestroy(sm_iter); } if (buf) { free(buf); } if (rv != 0) { /* something went wrong. do additional clean-up */ if (value_fields) { skFieldListDestroy(&value_fields); value_fields = NULL; } if (distinct_fields) { skFieldListDestroy(&distinct_fields); distinct_fields = NULL; } } return rv; } /* * is_duplicate = isFieldDuplicate(flist, fid, fcontext); * * Return 1 if the field-id 'fid' appears in the field-list * 'flist'. If 'fid' is SK_FIELD_CALLER, return 1 when a field in * 'flist' has the id SK_FIELD_CALLER and its context object points * to 'fcontext'. Return 0 otherwise. * * In this function, IPv4 and IPv6 fields are considered * equivalent; that is, you cannot have both SK_FIELD_SIPv4 and * SK_FIELD_SIPv6, and multiple SK_FIELD_CALLER fields are allowed. */ static int isFieldDuplicate( const sk_fieldlist_t *flist, sk_fieldid_t fid, const void *fcontext) { sk_fieldlist_iterator_t fl_iter; sk_fieldentry_t *fl_entry; skFieldListIteratorBind(flist, &fl_iter); switch (fid) { case SK_FIELD_SIPv4: case SK_FIELD_SIPv6: while ((fl_entry = skFieldListIteratorNext(&fl_iter)) != NULL) { switch (skFieldListEntryGetId(fl_entry)) { case SK_FIELD_SIPv4: case SK_FIELD_SIPv6: return 1; default: break; } } break; case SK_FIELD_DIPv4: case SK_FIELD_DIPv6: while ((fl_entry = skFieldListIteratorNext(&fl_iter)) != NULL) { switch (skFieldListEntryGetId(fl_entry)) { case SK_FIELD_DIPv4: case SK_FIELD_DIPv6: return 1; default: break; } } break; case SK_FIELD_NHIPv4: case SK_FIELD_NHIPv6: while ((fl_entry = skFieldListIteratorNext(&fl_iter)) != NULL) { switch (skFieldListEntryGetId(fl_entry)) { case SK_FIELD_NHIPv4: case SK_FIELD_NHIPv6: return 1; default: break; } } break; case SK_FIELD_CALLER: while ((fl_entry = skFieldListIteratorNext(&fl_iter)) != NULL) { if ((skFieldListEntryGetId(fl_entry) == (uint32_t)fid) && (skFieldListEntryGetContext(fl_entry) == fcontext)) { return 1; } } break; default: while ((fl_entry = skFieldListIteratorNext(&fl_iter)) != NULL) { if (skFieldListEntryGetId(fl_entry) == (uint32_t)fid) { return 1; } } break; } return 0; } static void schemaContextDestroy( void *vctx) { schema_context_t *ctx = (schema_context_t *)vctx; if (ctx) { sk_record_destroy(ctx->rec); sk_schema_timemap_destroy(ctx->timemap); sk_schemamap_destroy(ctx->map); sk_vector_destroy(ctx->key_fields); free(ctx); } } static void schemaCallback( sk_schema_t *schema, uint16_t UNUSED(tid), void *UNUSED(cbdata)) { schema_context_t *ctx; sk_field_ident_t id; const sk_field_t *f; sk_schema_t *new_schema; size_t i; int rv; /* Make sure there isn't already a context */ assert(sk_schema_get_context(schema) == NULL); /* Allocate a context */ ctx = sk_alloc(schema_context_t); ctx->key_fields = sk_vector_create(sizeof(const sk_field_t *)); sk_vector_set_capacity(ctx->key_fields, sk_vector_get_count(key_fields_vec)); rv = sk_schema_copy(&new_schema, schema); if (rv) { skAppPrintErr("Cannot copy schema: %s", sk_schema_strerror(rv)); exit(EXIT_FAILURE); } /* Ensure time fields */ rv = sk_schema_timemap_create(&ctx->timemap, new_schema); if (rv) { skAppPrintErr("Cannot create timemap: %s", sk_schema_strerror(rv)); exit(EXIT_FAILURE); } /* Populate key fields */ for (i = 0; i < sk_vector_get_count(key_fields_vec); ++i) { sk_vector_get_value(key_fields_vec, i, &id); f = sk_schema_get_field_by_ident(new_schema, id, NULL); sk_vector_append_value(ctx->key_fields, &f); } /* Determine time fields */ ctx->stime = sk_schema_get_field_by_ident( new_schema, FLOW_START_MILLISECONDS, NULL); assert(ctx->stime); ctx->etime = sk_schema_get_field_by_ident( new_schema, FLOW_END_MILLISECONDS, NULL); assert(ctx->etime); /* Determine bytes field */ ctx->bytes = sk_schema_get_field_by_ident( new_schema, OCTET_DELTA_COUNT, NULL); if (ctx->bytes == NULL) { ctx->bytes = sk_schema_get_field_by_ident( new_schema, OCTET_TOTAL_COUNT, NULL); } /* Determine packets field */ ctx->packets = sk_schema_get_field_by_ident( new_schema, PACKET_DELTA_COUNT, NULL); if (ctx->packets == NULL) { ctx->packets = sk_schema_get_field_by_ident( new_schema, PACKET_TOTAL_COUNT, NULL); } /* Determine IP address fields */ ctx->sipv4 = sk_schema_get_field_by_ident( new_schema, SOURCE_IPV4_ADDRESS, NULL); ctx->dipv4 = sk_schema_get_field_by_ident( new_schema, DESTINATION_IPV4_ADDRESS, NULL); ctx->nhipv4 = sk_schema_get_field_by_ident( new_schema, IP_NEXT_HOP_IPV4_ADDRESS, NULL); ctx->sipv6 = sk_schema_get_field_by_ident( new_schema, SOURCE_IPV6_ADDRESS, NULL); ctx->dipv6 = sk_schema_get_field_by_ident( new_schema, DESTINATION_IPV6_ADDRESS, NULL); ctx->nhipv6 = sk_schema_get_field_by_ident( new_schema, IP_NEXT_HOP_IPV6_ADDRESS, NULL); rv = sk_schema_freeze(new_schema); if (rv) { skAppPrintErr("Cannot freeze schema: %s", sk_schema_strerror(rv)); exit(EXIT_FAILURE); } rv = sk_schemamap_create_across_schemas(&ctx->map, new_schema, schema); if (rv) { skAppPrintErr("Cannot create mapping: %s", sk_schema_strerror(rv)); exit(EXIT_FAILURE); } rv = sk_record_create(&ctx->rec, new_schema); if (rv) { skAppPrintErr("Cannot create record: %s", sk_schema_strerror(rv)); exit(EXIT_FAILURE); } sk_schema_destroy(new_schema); /* Set the context */ sk_schema_set_context(schema, ctx, schemaContextDestroy); sk_schema_set_context(new_schema, ctx, NULL); } static sktime_t getSTimeFn( const rwRec *rwrec, void *UNUSED(x)) { schema_context_t *ctx; sktime_t t; ctx = (schema_context_t *)sk_schema_get_context( sk_record_get_schema(rwrec)); if (ctx->stime) { sk_record_get_datetime(rwrec, ctx->stime, &t); return t; } return 0; } static sktime_t getETimeFn( const rwRec *rwrec, void *UNUSED(x)) { schema_context_t *ctx; sktime_t t; ctx = (schema_context_t *)sk_schema_get_context( sk_record_get_schema(rwrec)); if (ctx->etime) { sk_record_get_datetime(rwrec, ctx->etime, &t); return t; } return 0; } static sktime_t getElapsedFn( const rwRec *rwrec, void *UNUSED(x)) { schema_context_t *ctx; sktime_t s, e; ctx = (schema_context_t *)sk_schema_get_context( sk_record_get_schema(rwrec)); if (ctx->stime && ctx->etime) { sk_record_get_datetime(rwrec, ctx->stime, &s); sk_record_get_datetime(rwrec, ctx->etime, &e); return e - s; } return 0; } static uint64_t getBytesFn( const rwRec *rwrec, void *UNUSED(x)) { schema_context_t *ctx; uint64_t val; ctx = (schema_context_t *)sk_schema_get_context( sk_record_get_schema(rwrec)); if (ctx->bytes) { sk_record_get_unsigned64(rwrec, ctx->bytes, &val); return val; } return 0; } static uint64_t getPacketsFn( const rwRec *rwrec, void *UNUSED(x)) { schema_context_t *ctx; uint64_t val; ctx = (schema_context_t *)sk_schema_get_context( sk_record_get_schema(rwrec)); if (ctx->packets) { sk_record_get_unsigned64(rwrec, ctx->packets, &val); return val; } return 0; } static int prepareFileForRead( skstream_t *rwios) { int rv; if (app_flags.print_filenames) { fprintf(PRINT_FILENAMES_FH, "%s\n", skStreamGetPathname(rwios)); } if (copy_input) { skStreamSetCopyInput(rwios, copy_input); } skStreamSetIPv6Policy(rwios, ipv6_policy); rv = skStreamSetNewSchemaCallback(rwios, schemaCallback, NULL); assert(rv == 0); return 0; } /* * int = appNextInput(&rwios); * * Fill 'rwios' with the next input file to read. Return 0 if * 'rwios' was successfully opened, 1 if there are no more input * files, or -1 if an error was encountered. */ int appNextInput( skstream_t **rwios) { char *path = NULL; int rv; rv = skOptionsCtxNextArgument(optctx, &path); if (0 == rv) { if ((rv = skStreamCreate(rwios, SK_IO_READ, SK_CONTENT_UNKNOWN_FLOW)) || (rv = skStreamBind(*rwios, path)) || (rv = skStreamOpen(*rwios))) { skStreamPrintLastErr(*rwios, rv, &skAppPrintErr); skStreamDestroy(rwios); return -1; } (void)prepareFileForRead(*rwios); } return rv; } /* * setOutputHandle(); * * If using the pager, enable it and bind it to the Ascii stream. */ void setOutputHandle(void) { int rv; /* only invoke the pager when the user has not specified the * output-path, even if output-path is stdout */ if (NULL == output.of_name) { /* invoke the pager */ rv = skFileptrOpenPager(&output, pager); if (rv && rv != SK_FILEPTR_PAGER_IGNORED) { skAppPrintErr("Unable to invoke pager"); } } } /* ** Local Variables: ** mode:c ** indent-tabs-mode:nil ** c-basic-offset:4 ** End: */