/* ** Copyright (C) 2001-2007 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.225-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 F19628-00-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@ */ /* ** rwcututils.c ** utility routines in support of rwcut. ** Suresh Konda ** */ #include "silk.h" RCSIDENT("$SiLK: rwcututils.c 8269 2007-08-03 18:54:48Z mthomas $"); #include "rwcut.h" /* TYPEDEFS AND MACROS */ /* where to write --help output */ #define USAGE_FH stdout /* The last field printed by default. */ #define RWCUT_LAST_DEFAULT_FIELD RWREC_FIELD_SID /* LOCAL FUNCTIONS */ static void appUsageLong(void); static int appOptionsHandler(clientData cData, int opt_index, char *opt_arg); static void usageFields(FILE *fh); static void setAsciiStreamProperties(void); static int useDefaultOutput(void); static int outStreamsAddAsciiStream( sk_vector_t *outputs_vec, rwAsciiStream_t *a_stream, const uint32_t *field_list, uint32_t field_count); static int outStreamsAddCutFunc( sk_vector_t *outputs_vec, uint32_t field_id); static int parseFields(const char* opt_arg); static int appAddDynlib(const char *dlpath, int warnOnError); static app_dynlib_t* fieldNum2Dynlib(uint32_t fieldNum); /* LOCAL VARIABLES */ /* the maximum ID in the field_names string-map */ static uint32_t max_field_id = 0; /* name of program to run to page output */ static char *pager = NULL; /* are we using the pager? */ static int using_pager = 0; /* whether to use legacy (1), new-style (0), or default (-1) timestamps */ static int legacy_timestamps = -1; /* OPTIONS SETUP */ typedef enum { /* Keep this list in sync with appOptions[] */ OPT_FIELDS, OPT_INTEGER_IPS, OPT_ZERO_PAD_IPS, OPT_NUM_RECS, OPT_START_REC_NUM, OPT_END_REC_NUM, OPT_NO_TITLES, OPT_NO_COLUMNS, OPT_COLUMN_SEPARATOR, OPT_DELIMITED, OPT_EPOCH_TIME, OPT_ICMP_TYPE_AND_CODE, OPT_DYNAMIC_LIB_VALUE, OPT_INTEGER_SENSORS, OPT_DRY_RUN, OPT_PRINT_FILENAMES, OPT_COPY_INPUT, OPT_OUTPUT_PATH, OPT_PAGER, OPT_LEGACY_TIMESTAMPS } appOptionsEnum; static struct option appOptions[] = { {"fields", REQUIRED_ARG, 0, OPT_FIELDS}, {"integer-ips", NO_ARG, 0, OPT_INTEGER_IPS}, {"zero-pad-ips", NO_ARG, 0, OPT_ZERO_PAD_IPS}, {"num-recs", REQUIRED_ARG, 0, OPT_NUM_RECS}, {"start-rec-num", REQUIRED_ARG, 0, OPT_START_REC_NUM}, {"end-rec-num", REQUIRED_ARG, 0, OPT_END_REC_NUM}, {"no-titles", NO_ARG, 0, OPT_NO_TITLES}, {"no-columns", NO_ARG, 0, OPT_NO_COLUMNS}, {"column-separator", REQUIRED_ARG, 0, OPT_COLUMN_SEPARATOR}, {"delimited", OPTIONAL_ARG, 0, OPT_DELIMITED}, {"epoch-time", NO_ARG, 0, OPT_EPOCH_TIME}, {"icmp-type-and-code", NO_ARG, 0, OPT_ICMP_TYPE_AND_CODE}, {OPT_DYNAMIC_LIBRARY, REQUIRED_ARG, 0, OPT_DYNAMIC_LIB_VALUE}, {"integer-sensors", NO_ARG, 0, OPT_INTEGER_SENSORS}, {"dry-run", NO_ARG, 0, OPT_DRY_RUN}, {"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}, {"legacy-timestamps", OPTIONAL_ARG, 0, OPT_LEGACY_TIMESTAMPS}, {0,0,0,0} /* sentinel entry */ }; static char *appHelp[] = { NULL, /* generated dynamically */ "Print IP numbers as integers. Def. No", "Print IP numbers as zero-padded dotted decimal. Def. No", "Number of records to print, or 0 to print all. Def. 0", "Starting record number. Def. 1", "Ending record number. Def. None", "Do not print column headers. Def. Print titles.", "Disable fixed-width columnar output. Def. Columnar", "Use specified character between columns. Def. '|'", "Shortcut for --no-columns --column-sep=CHAR", "Print times in UNIX epoch seconds. Def. No", "Print ICMP type/code in sPort/dPort fields. Def. No", "Use given dynamic library. Def. None", "Print sensor as an integer. Def. No", "Parse options and print column titles only. Def. No", "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", ("Timestamp format. Choices:\n" "\t0==(new)\"YYYY/MM/DDThh:mm:ss.sss\";" " 1==(legacy)\"MM/DD/YYYY hh:mm:ss\".\n" "\tDef. " #ifdef SK_ENABLE_LEGACY_TIMESTAMP "1" #else "0" #endif " when switch not provided; 1 when switch provided with no value"), (char *)NULL }; /* FUNCTION DEFINITIONS */ /* * appUsageLong(); * * Print complete usage information to USAGE_FH. Pass this * function to skOptionsSetUsageCallback(); optionsParse() will * call this funciton and then exit the program when the --help * option is given. */ static void appUsageLong(void) { #define USAGE_MSG \ ("[SWITCHES] [FILES]\n" \ "\tPrint SiLK Flow records in a |-delimited, columnar, human-readable\n" \ "\tformat. Use --fields to select columns to print. When no files are\n"\ "\tgiven on the command line, flows are read from the standard input.\n") FILE *fh = USAGE_FH; int i; fprintf(fh, "%s %s", skAppName(), USAGE_MSG); fprintf(fh, "\nSWITCHES:\n"); skOptionsDefaultUsage(fh); sksiteOptionsUsage(fh); for (i = 0; appOptions[i].name; i++ ) { fprintf(fh, "--%s %s. ", appOptions[i].name, SK_OPTION_HAS_ARG(appOptions[i])); switch (appOptions[i].val) { case OPT_FIELDS: /* Dynamically build the help */ usageFields(fh); break; default: /* Simple static help text from the appHelp array */ fprintf(fh, "%s\n", appHelp[i]); break; } } for (i = 0; i < app_dynlib_count; ++i) { dynlibOptionsUsage(app_dynlib_list[i].dlisp, fh); } } /* * appTeardown() * * Teardown all modules, close all files, and tidy up all * application state. * * This function is idempotent. */ void appTeardown(void) { static uint8_t teardownFlag = 0; int j; if (teardownFlag) { return; } teardownFlag = 1; /* Dynamic library teardown */ for (j = 0; j < app_dynlib_count; ++j) { dynlibTeardown(app_dynlib_list[j].dlisp); app_dynlib_list[j].dlisp = NULL; } app_dynlib_count = 0; if (rwIOS) { rwCloseFile(rwIOS); } rwIOS = (rwIOStruct_t*)NULL; /* close the pager */ if (using_pager) { skClosePager(stream_out, pager); } /* close the output-path */ if (ioISP->passCount && ioISP->passFD[0] && ioISP->passFD[0] != stdout) { if (ioISP->passIsPipe[0]) { if (-1 == pclose(ioISP->passFD[0])) { skAppPrintErr("Error closing output pipe %s", ioISP->passFPath[0]); } } else { if (EOF == fclose(ioISP->passFD[0])) { skAppPrintSyserror("Error closing output file %s", ioISP->passFPath[0]); } } ioISP->passFD[0] = NULL; } /* destroy output */ if (outputs) { size_t i; out_stream_t *ostream; for (i = 0; i < output_count; ++i) { ostream = outputs[i]; if (ostream->a_stream) { rwAsciiStreamDestroy(&(ostream->a_stream)); } free(ostream); } free(outputs); outputs = NULL; } /* destroy field map */ if (field_map != NULL) { skStringMapDestroy(field_map); field_map = NULL; } iochecksTeardown(ioISP); skAppUnregister(); } /* * 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) { int j; /* verify same number of options and help strings */ assert((sizeof(appHelp)/sizeof(char *)) == (sizeof(appOptions)/sizeof(struct option))); /* register the application */ skAppRegister(argv[0]); skOptionsSetUsageCallback(&appUsageLong); /* initialize globals */ memset(&cut_opts, 0, sizeof(cut_opts)); delimiter = '|'; outputs = NULL; output_count = 0; stream_out = stdout; /* initialize string-map of field identifiers */ if (rwAsciiFieldMapAddDefaultFields(&field_map)) { skAppPrintErr("unable to setup fields stringmap"); exit(EXIT_FAILURE); } max_field_id = RWREC_PRINTABLE_FIELD_COUNT - 1; /* do not allow ANY output files other than stdout */ ioISP = iochecksSetup(1, 0, argc, argv); /* register the options */ if (optionsRegister(appOptions, (optHandler)appOptionsHandler, NULL) || sksiteOptionsRegister(SK_SITE_FLAG_CONFIG_FILE)) { skAppPrintErr("unable to register options"); exit(EXIT_FAILURE); } /* try to load run-time dynamic libraries */ for (j = 0; app_dynlib_names[j]; ++j) { appAddDynlib(app_dynlib_names[j], 0); } /* parse options */ ioISP->firstFile = optionsParse(argc, argv); if (ioISP->firstFile < 0) { skAppUsage(); /* never returns */ } /* Use a particular timestamp if requested */ if (legacy_timestamps == 0) { sktimestampSetMode(SK_TIMESTAMP_YYYYMMDD, SK_TIME_FRACSEC_ON); } else if (legacy_timestamps == 1) { sktimestampSetMode(SK_TIMESTAMP_MMDDYYYY, SK_TIME_FRACSEC_OFF); } else { sktimestampSetMode(SK_TIMESTAMP_DEFAULT, SK_TIME_FRACSEC_DEFAULT); } /* Not having site config is allowed */ sksiteConfigure(0); /* set default output if --fields was not given */ if ((outputs == NULL) && (0 != useDefaultOutput())) { skAppPrintErr("Cannot set default output"); exit(EXIT_FAILURE); } /* if no destination was specified, use stdout */ if ((0 == ioISP->passCount) && iochecksPassDestinations(ioISP, "stdout", 1)) { exit(EXIT_FAILURE); } stream_out = ioISP->passFD[0]; /* Invoke the pager */ using_pager = skOpenPagerWhenStdoutTty(&stream_out, &pager); if (using_pager < 0) { exit(EXIT_FAILURE); } /* set properties on all the ascii-streams in outputs */ setAsciiStreamProperties(); /* * if dry-run, print the column titles and exit */ if (cut_opts.dry_run) { writeColTitles(); appTeardown(); exit(EXIT_SUCCESS); } /* * set numRecs to infinity if user did not provide a value. * Ignore output device since the pager should take care of that * now. */ if (globalCounters.numRecs == 0) { globalCounters.numRecs = 0xFFFFFFFF; } /* Use STDIN as an input stream if it is not a TTY; make certain * we have some input and we are either reading from STDIN or * using files listed the command line, but not both. */ if (iochecksAcceptFromStdin(ioISP) || iochecksInputs(ioISP, 0)) { skAppUsage(); } /* looks good, open the --copy-input destination */ if (iochecksOpenCopyDest(ioISP)) { exit(EXIT_FAILURE); } if (atexit(appTeardown) < 0) { skAppPrintErr("unable to register appTeardown() with atexit()"); appTeardown(); exit(EXIT_FAILURE); } return; /* OK */ } /* * status = appOptionsHandler(cData, opt_index, opt_arg); * * This function is passed to optionsRegister(); it will be called * by optionsParse() for each user-specified switch that the * application has registered; it should handle the switch as * required---typically by setting global variables---and return 1 * if the switch processing failed or 0 if it succeeded. Returning * a non-zero from from the handler causes optionsParse() 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) { uint32_t nrecs; switch ((appOptionsEnum)opt_index) { case OPT_FIELDS: return parseFields(opt_arg); case OPT_INTEGER_IPS: /* integer ip */ cut_opts.integer_ips = 1; break; case OPT_ZERO_PAD_IPS: cut_opts.zero_pad_ips = 1; break; case OPT_NUM_RECS: /* num-recs */ if (skStringParseUint32(&nrecs, opt_arg, 0, 0)) { skAppPrintErr("Invalid argument for --%s: '%s'", appOptions[opt_index].name, opt_arg); return 1; } if (nrecs == 0) { /* all records */ globalCounters.numRecs = 0xFFFFFFFF; } else { globalCounters.numRecs = nrecs; } break; case OPT_START_REC_NUM: /* start-rec number */ if (skStringParseUint32(&globalCounters.firstRec, opt_arg, 0, 0)) { skAppPrintErr("Invalid argument for %s: '%s'", appOptions[opt_index].name, opt_arg); return 1; } break; case OPT_END_REC_NUM: /* end rec */ if (skStringParseUint32(&globalCounters.lastRec, opt_arg, 0, 0)) { skAppPrintErr("Invalid argument for %s: '%s'", appOptions[opt_index].name, opt_arg); return 1; } break; case OPT_NO_TITLES: /* no titles */ cut_opts.no_titles = 1; break; case OPT_NO_COLUMNS: cut_opts.no_columns = 1; break; case OPT_COLUMN_SEPARATOR: delimiter = opt_arg[0]; break; case OPT_DELIMITED: /* dump as delimited text */ cut_opts.no_columns = 1; if (opt_arg) { delimiter = opt_arg[0]; } break; case OPT_EPOCH_TIME: /* epoch time */ cut_opts.epoch_time = 1; break; case OPT_ICMP_TYPE_AND_CODE: /* type and code */ cut_opts.icmpTandC = 1; break; case OPT_DYNAMIC_LIB_VALUE: /* dynamic library */ if (appAddDynlib(opt_arg, 1)) { return 1; } break; case OPT_INTEGER_SENSORS: cut_opts.integerSensor = 1; break; case OPT_DRY_RUN: cut_opts.dry_run = 1; break; case OPT_PRINT_FILENAMES: cut_opts.printFileName = 1; break; case OPT_COPY_INPUT: if (iochecksAllDestinations(ioISP, opt_arg)) { return 1; } break; case OPT_OUTPUT_PATH: if (iochecksPassDestinations(ioISP, opt_arg, 1)) { return 1; } break; case OPT_PAGER: pager = opt_arg; break; case OPT_LEGACY_TIMESTAMPS: if (opt_arg == NULL) { legacy_timestamps = 1; } else if (opt_arg[0] == '1') { legacy_timestamps = 1; } else if (opt_arg[0] == '0') { legacy_timestamps = 0; } else { skAppPrintErr("Invalid value for %s: '%s'.", appOptions[opt_index].name, opt_arg); return 1; } break; } /* switch */ return 0; /* OK */ } /* * setAsciiStreamProperties(); * * Use the switches entered by the user to set properties on all * the ascii-streams that appear in the global 'outputs' array. */ static void setAsciiStreamProperties(void) { uint32_t i; rwAsciiStream_t *a_stream; int ip_warning_printed = 0; int icmp_t_and_c = 0; for (i = 0; i < output_count; ++i) { if (outputs[i]->a_stream == NULL) { /* skip non-ascii streams */ continue; } /* only ascii streams make it here */ a_stream = outputs[i]->a_stream; /* properties for every stream */ rwAsciiSetNoNewline(a_stream); rwAsciiSetDelimiter(a_stream, delimiter); rwAsciiSetOutputHandle(a_stream, stream_out); /* simple properties */ if (cut_opts.no_titles) { rwAsciiSetNoTitles(a_stream); } if (cut_opts.no_columns) { rwAsciiSetNoColumns(a_stream); } if (cut_opts.epoch_time) { rwAsciiSetEpochTime(a_stream); } if (cut_opts.integerSensor) { rwAsciiSetIntegerSensors(a_stream); } /* conflicting properties */ if (cut_opts.zero_pad_ips && !cut_opts.integer_ips) { rwAsciiSetZeroPadIps(a_stream); } if (cut_opts.integer_ips) { if ((cut_opts.zero_pad_ips) && (ip_warning_printed == 0)) { ip_warning_printed = 1; skAppPrintErr("--integer-ips option overrides" " --zero-pad-ips\n" "\t Will print IPs as integers."); } rwAsciiSetIntegerIps(a_stream); } if ((cut_opts.icmpTandC) && (icmp_t_and_c == 0)) { icmp_t_and_c = 1; /* would prefer to do this in the ascii-stream where the * user has used sport/dport columns */ rwAsciiSetIcmpTypeCode(a_stream); } } } /* * status = useDefaultOutput(); * * Set the global 'output' array to a single element that contains * an ascii-stream printing rwcut's default columns. Return 0 on * success or -1 on memory error. */ static int useDefaultOutput(void) { uint32_t default_fields[1+RWCUT_LAST_DEFAULT_FIELD]; const uint32_t count = 1+RWCUT_LAST_DEFAULT_FIELD; uint32_t i; /* set up the default fields for rwcut */ for (i = 0; i < count; ++i) { default_fields[i] = i; } /* create output as array of out_stream_t; create its first element */ outputs = calloc(1, sizeof(out_stream_t*)); if (NULL == outputs) { return -1; } outputs[0] = calloc(1, sizeof(out_stream_t)); if (NULL == outputs[0]) { free(outputs); return -1; } /* create ascii-stream */ if (0 != rwAsciiStreamCreate(&(outputs[0]->a_stream))) { free(outputs[0]); free(outputs); return -1; } rwAsciiSetFields(outputs[0]->a_stream, default_fields, count); /* set global output count */ output_count = 1; return 0; } /* * status = outStreamsAddAsciiStream(outputs_vec, a_stream, list, count); * * Create a new out_stream_t and add it to the vector of output * streams, 'outputs_vec'. The vector must already exist. * * The new out_stream_t is a wrapper around the ascii-stream * 'a_stream'; 'list' is an array of integers that contains the * fields of the 'a_stream' to be printed; 'count' is the number of * elements in 'list'. * * Returns 0 on success, or -1 for a memory allocation error. */ static int outStreamsAddAsciiStream( sk_vector_t *outputs_vec, rwAsciiStream_t *a_stream, const uint32_t *field_list, uint32_t field_count) { out_stream_t *ostream; ostream = calloc(1, sizeof(out_stream_t)); if (ostream == NULL) { return -1; } rwAsciiSetFields(a_stream, field_list, field_count); ostream->a_stream = a_stream; if (0 != skVectorAppendValue(outputs_vec, &ostream)) { free(ostream); return -1; } return 0; } /* * status = outStreamsAddCutFunc(outputs_vec, field_id); * * Create a new out_stream_t and add it to the vector of output * streams, 'outputs_vec'. The vector must already exist. * * The new out_stream_t is a wrapper around the 'cut_fn' provided * by the plug-in (run-time dynamic library) that provides the * field numbered 'field_id'. * * Returns 0 on success, or -1 for a memory allocation error or if * the plug-in that provides the field numbered 'field_id' cannot * be found. */ static int outStreamsAddCutFunc( sk_vector_t *outputs_vec, uint32_t field_id) { out_stream_t *ostream; cutf_t cut_fn; app_dynlib_t *cutDL; int width; rwRec dummy_rec; cutDL = fieldNum2Dynlib(field_id); if (NULL == cutDL) { assert(cutDL != NULL); return -1; } /* Initialize the dynamic library */ if (dynlibInitialize(cutDL->dlisp)) { sk_vector_t *entries_vec; sk_stringmap_entry_t *entry = NULL; entries_vec = skVectorNew(sizeof(sk_stringmap_entry_t*)); if (NULL == entries_vec) { skAppPrintErr(("field not available--cannot initialize %s"), dynlibGetPath(cutDL->dlisp)); return -1; } skStringMapGetNames(entries_vec, field_map, field_id); skVectorGetValue(&entry, entries_vec, 0); if (NULL != entry) { skAppPrintErr(("field '%s' not available--cannot initialize %s"), entry->name, dynlibGetPath(cutDL->dlisp)); } skVectorClear(entries_vec); return -1; } cut_fn = cutDL->fxn; field_id -= cutDL->offset; /* get the required width of the column; but don't allow a value * larger than the maximum column width */ width = cut_fn(field_id, NULL, 0, &dummy_rec); if (width == 0 || width > RWCUT_MAX_COLUMN_WIDTH) { width = RWCUT_MAX_COLUMN_WIDTH; } ostream = calloc(1, sizeof(out_stream_t)); if (ostream == NULL) { return -1; } ostream->cutf_wrap.cut_fxn = cut_fn; ostream->cutf_wrap.field_id = field_id; ostream->cutf_wrap.field_width = width; if (0 != skVectorAppendValue(outputs_vec, &ostream)) { free(ostream); return -1; } return 0; } /* * status = parseFields(fields_string); * * Parse the user's option for the --fields switch and fill in the * global 'outputs[]' array of out_stream_t's. Return 0 on * success; 1 on failure. */ static int parseFields(const char *field_string) { /* the fields (columns) to print in the order to print them; each * value is an ID from field_map */ uint32_t *field_list = NULL; /* number of fields to print; length of field_list */ uint32_t field_count; sk_vector_t *outputs_vec = NULL; int rv = 1; /* return value; assume failure */ size_t i; rwAsciiStream_t *current_stream = NULL; int first_position = 0; /* have we been here before? */ if (outputs != NULL) { skAppPrintErr("--%s option given multiple times", appOptions[OPT_FIELDS].name); return 1; } if (field_string == NULL || field_string[0] == '\0') { skAppPrintErr("missing --fields value"); return 1; } if (rwAsciiFieldMapParseFields(&field_list, &field_count, field_string, field_map, 0)) { /* library printed the error */ return 1; } /* create vector to hold outputs */ outputs_vec = skVectorNew(sizeof(out_stream_t*)); if (outputs_vec == NULL) { goto END; } for (i = 0; i < field_count; ++i) { if (field_list[i] < RWREC_PRINTABLE_FIELD_COUNT) { /* field is built-in */ if (current_stream == NULL) { /* create ascii-stream */ if (0 != rwAsciiStreamCreate(¤t_stream)) { skAppPrintErr("Cannot create stream"); goto END; } first_position = i; } } else { /* field comes from a plug-in */ /* first, close the current_stream */ if (current_stream != NULL) { rv = outStreamsAddAsciiStream(outputs_vec, current_stream, &field_list[first_position], i - first_position); if (rv != 0) { goto END; } current_stream = NULL; } rv = outStreamsAddCutFunc(outputs_vec, field_list[i]); if (rv != 0) { goto END; } } } /* all done; close the final_stream */ if (current_stream != NULL) { rv = outStreamsAddAsciiStream(outputs_vec, current_stream, &field_list[first_position], i - first_position); if (rv != 0) { goto END; } current_stream = NULL; } output_count = skVectorGetCount(outputs_vec); outputs = calloc(output_count, sizeof(out_stream_t*)); if (outputs == NULL) { goto END; } skVectorToArray(outputs, outputs_vec); /* successful */ rv = 0; END: if (outputs_vec != NULL) { if (rv != 0) { /* clean up all the out_stream_t's on error */ output_count = skVectorGetCount(outputs_vec); for (i = 0; i < output_count; ++i) { out_stream_t *o_stream; skVectorGetValue(&o_stream, outputs_vec, i); if (o_stream->a_stream) { rwAsciiStreamDestroy(&(o_stream->a_stream)); } free(o_stream); } } skVectorDestroy(outputs_vec); } if (field_list != NULL) { free(field_list); } if (current_stream != NULL) { rwAsciiStreamDestroy(¤t_stream); } return rv; } /* * usageFields(fh); * * Print the usage (help) message for --fields to the 'fh' file pointer */ static void usageFields(FILE *fh) { sk_vector_t *entries_vec; sk_stringmap_entry_t *entry; uint32_t i; fprintf(fh, "Field(s) to print. List columns separated by commas:\n"); rwAsciiFieldMapPrintUsage(fh, field_map); /* Print default fields */ fprintf(fh, "\tDef: "); entries_vec = skVectorNew(sizeof(sk_stringmap_entry_t*)); if (!entries_vec) { return; } for (i = 0; i < RWCUT_LAST_DEFAULT_FIELD; ++i) { skStringMapGetNames(entries_vec, field_map, i); skVectorGetValue(&entry, entries_vec, 0); if (i > 0) { fprintf(fh, ","); } fprintf(fh, "%s", entry->name); skVectorClear(entries_vec); } fprintf(fh, "\n"); skVectorDestroy(entries_vec); } /* * appAddDynlib(dlpath, warnOnError); * * Load the dynamic library at dlpath, store its information in the * app_dynlib_list, and increment the app_dynlib_count. Return 0 on * success. Return 1 if the library cannot be loaded or if too many * dynamic libraries have been loaded. */ static int appAddDynlib(const char *dlpath, int warnOnError) { dynlibInfoStruct *dlisp = NULL; cutf_t cutf; sk_stringmap_entry_t *entries = NULL; int i; int count; char title[64]; uint32_t offset; int rv = 1; /* return value; assume error */ /* fields from the plug-in are added above this value. All fields * from the plug-in must have an ID > 0. */ offset = max_field_id; /* too many plug-ins? */ if (app_dynlib_count == APP_MAX_DYNLIBS) { skAppPrintErr("Too many dynlibs. Only %u allowed", APP_MAX_DYNLIBS); goto END; } /* load it */ dlisp = dynlibCreate(DYNLIB_CUT); if (dynlibLoad(dlisp, dlpath)) { /* failed to find library, missing symbols, or setup failed */ if (warnOnError) { skAppPrintErr("Error in dynamic library: %s", dynlibLastError(dlisp)); } goto END; } cutf = (cutf_t)dynlibGetRWProcessor(dlisp); /* dynlibCheck() should have made this check already */ assert(cutf != NULL); /* get the fields for rwcut */ if (dynlibConfigure(dlisp, (void*)(&entries)) != 0) { skAppPrintErr("Error getting fields from plug-in %s", dynlibGetPath(dlisp)); goto END; } if (entries != NULL) { /* the dynlibConfigure() routine gave us the fields */ for (i = 0; entries[i].name != NULL; ++i) { /* check that value is not too small or too big */ if (entries[i].id == 0) { skAppPrintErr("Invalid id '0' for field '%s' in %s plugin", entries[i].name, dynlibGetPath(dlisp)); goto END; } if (offset >= (UINT32_MAX - entries[i].id)) { skAppPrintErr("Invalid id '%u' for field '%s' in %s plugin", entries[i].id, entries[i].name, dynlibGetPath(dlisp)); goto END; } /* add it */ if (SKSTRINGMAP_OK != skStringMapAddID(field_map, entries[i].name, offset + entries[i].id)) { skAppPrintErr("Cannot add entries to map"); goto END; } /* keep track of the maximum field id */ if (max_field_id < (offset + entries[i].id)) { max_field_id = offset + entries[i].id; } } } else { /* the dynlibConfigure() routine did not give us the fields, * so fall-back to the old way of getting them */ count = cutf(0, NULL, 0, NULL); /* Number of fields */ for (i = 1; i <= count; ++i) { cutf(i, title, sizeof(title), NULL); if (SKSTRINGMAP_OK != skStringMapAddID(field_map, title, offset+i)) { skAppPrintErr("Cannot add entries to map"); goto END; } } max_field_id += count; } /* success, add the dynamic library */ app_dynlib_list[app_dynlib_count].dlisp = dlisp; app_dynlib_list[app_dynlib_count].fxn = cutf; app_dynlib_list[app_dynlib_count].offset = offset; ++app_dynlib_count; rv = 0; END: if ((rv != 0) && (dlisp != NULL)) { sk_link_err_t link_rv; sk_link_item_t *node; sk_stringmap_entry_t *entry; dynlibTeardown(dlisp); /* roll back the field_map to those whose IDs are <= offset */ link_rv = skLinkGetHead(&node, field_map); while (link_rv == SKLINK_OK) { skLinkGetData((void**)&entry, node); link_rv = skLinkGetNext(&node, node); if (entry->id > offset) { /* remove the entry */ skStringMapRemoveByName(field_map, entry->name); } } max_field_id = offset; } return rv; } /* * fieldNum2Dynlib(field_id); * * Given a field number, return a pointer to a app_dynlib_t from the * app_dynlib_list[] that points to the dynamic library used to print * the field_id field. Return NULL if the field_id is not related * to any dynamic library. */ static app_dynlib_t* fieldNum2Dynlib(uint32_t field_id) { int j; if (!app_dynlib_count || (field_id <= app_dynlib_list[0].offset)) { return NULL; } for (j = 1; j < app_dynlib_count; ++j) { if (field_id <= app_dynlib_list[j].offset) { break; } } return &(app_dynlib_list[j-1]); } /* ** Local Variables: ** mode:c ** indent-tabs-mode:nil ** c-basic-offset:4 ** End: */