|
@@ -0,0 +1,485 @@
|
|
|
|
+/*
|
|
|
|
+ * Copyright (C) 2011 Internet Systems Consortium, Inc. ("ISC")
|
|
|
|
+ *
|
|
|
|
+ * Permission to use, copy, modify, and/or distribute this software for any
|
|
|
|
+ * purpose with or without fee is hereby granted, provided that the above
|
|
|
|
+ * copyright notice and this permission notice appear in all copies.
|
|
|
|
+ *
|
|
|
|
+ * THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
|
|
|
|
+ * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
|
|
|
+ * AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
|
|
|
|
+ * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
|
|
|
+ * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
|
|
|
|
+ * OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
|
|
|
+ * PERFORMANCE OF THIS SOFTWARE.
|
|
|
|
+ */
|
|
|
|
+
|
|
|
|
+#include <string.h>
|
|
|
|
+#include <stdlib.h>
|
|
|
|
+#include <stdio.h>
|
|
|
|
+#include <ctype.h>
|
|
|
|
+#include <sys/types.h>
|
|
|
|
+#include <stdarg.h>
|
|
|
|
+#include <unistd.h>
|
|
|
|
+#include <errno.h>
|
|
|
|
+#include "procconf.h"
|
|
|
|
+
|
|
|
|
+static char errmsg[256]; /* for returning error descriptions */
|
|
|
|
+static const char* pc_name;
|
|
|
|
+static const char* pc_usage;
|
|
|
|
+
|
|
|
|
+#define INTERNAL_ERROR -1
|
|
|
|
+#define USAGE_ERROR -2
|
|
|
|
+
|
|
|
|
+/*
|
|
|
|
+ * error: printf-style interface to print an error message or write it into
|
|
|
|
+ * global errmsg. If a usage message has been given (the indicator that errors
|
|
|
|
+ * should be handled internally), the message is printed to stderr and the
|
|
|
|
+ * program is exited. If not, it is written into errmsg.
|
|
|
|
+ * Input variables:
|
|
|
|
+ * errtype is the error type. If USAGE_ERROR, the program's usage is included
|
|
|
|
+ * in error messages, and the exit status is 2; otherwise the exit status
|
|
|
|
+ * is 1.
|
|
|
|
+ * format and the remaining arguments are as for printf().
|
|
|
|
+ */
|
|
|
|
+static void
|
|
|
|
+error(const int errtype, const char* format, ...) {
|
|
|
|
+ va_list ap;
|
|
|
|
+
|
|
|
|
+ va_start(ap,format);
|
|
|
|
+ if (pc_usage != NULL) { /* error messages should be printed directly */
|
|
|
|
+ fprintf(stderr, "%s: ", pc_name);
|
|
|
|
+ vfprintf(stderr, format, ap);
|
|
|
|
+ putc('\n', stderr);
|
|
|
|
+ if (errtype == USAGE_ERROR) {
|
|
|
|
+ fputs(pc_usage, stderr);
|
|
|
|
+ }
|
|
|
|
+ exit(errtype == USAGE_ERROR ? 2 : 1);
|
|
|
|
+ } else {
|
|
|
|
+ vsnprintf(errmsg, sizeof(errmsg), format, ap);
|
|
|
|
+ }
|
|
|
|
+ va_end(ap);
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+/*
|
|
|
|
+ * Allocate memory, with error checking.
|
|
|
|
+ * On allocation failure, error() is called; see its description.
|
|
|
|
+ * The memory is zeroed before being returned.
|
|
|
|
+ */
|
|
|
|
+static void*
|
|
|
|
+pc_malloc(size_t size) {
|
|
|
|
+ void* ret = calloc(1, size);
|
|
|
|
+ if (ret == NULL) {
|
|
|
|
+ error(INTERNAL_ERROR, "Out of memory");
|
|
|
|
+ }
|
|
|
|
+ return(ret);
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+/*
|
|
|
|
+ * Generate an error message describing an error in the value passed with an
|
|
|
|
+ * option. This is a front end for error(); see its usage for details.
|
|
|
|
+ * Input variables:
|
|
|
|
+ * expected: A description of what was expected for the value.
|
|
|
|
+ * varDesc: The descriptor for the option.
|
|
|
|
+ * value: The value that was given with the option.
|
|
|
|
+ * detail: If non-null, additional detail to append regardign the problem.
|
|
|
|
+ */
|
|
|
|
+static void
|
|
|
|
+opterror(const char* expected, const char* value, const confvar_t* varDesc,
|
|
|
|
+ const char* detail) {
|
|
|
|
+ if (detail == NULL) {
|
|
|
|
+ detail = "";
|
|
|
|
+ }
|
|
|
|
+ error(USAGE_ERROR,
|
|
|
|
+ "Invalid value given for option -%c: expected %s, got: %s%s",
|
|
|
|
+ varDesc->outind, expected, value, detail);
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+/*
|
|
|
|
+ * Add an option flag or value assignment to the options database.
|
|
|
|
+ * This does all option-type-specific processing, and generates linked lists
|
|
|
|
+ * of option structures.
|
|
|
|
+ *
|
|
|
|
+ * Input variables:
|
|
|
|
+ * value is the string value assigned to the option, for options that take
|
|
|
|
+ * values.
|
|
|
|
+ * varDesc is the description structure for this option.
|
|
|
|
+ *
|
|
|
|
+ * Output variables:
|
|
|
|
+ * The option data is stored in a malloced confval structure, which is appended
|
|
|
|
+ * to the option list. The first element of the list is pointed to by first.
|
|
|
|
+ * first should be NULL if the list contains no elements; in this case it is
|
|
|
|
+ * set to point to the new structure. Otherwise the structure is appended to
|
|
|
|
+ * the element pointed to by last (it is updated to point to that structure).
|
|
|
|
+ * The 'next' element of the new structure is set to NULL; this is the list
|
|
|
|
+ * termination indicator when traversing it.
|
|
|
|
+ *
|
|
|
|
+ * Return value:
|
|
|
|
+ * 0 if option was ignored.
|
|
|
|
+ * 1 if option was processed & added to option chain.
|
|
|
|
+ * On error, a string describing the error is stored in the global errmsg and
|
|
|
|
+ * -1 is returned.
|
|
|
|
+ */
|
|
|
|
+static int
|
|
|
|
+addOptVal(const char* value, const confvar_t* varDesc,
|
|
|
|
+ confval** first, confval** last) {
|
|
|
|
+ const void* addr; /* address, if any at which to store value */
|
|
|
|
+ confval data; /* data for this option/value */
|
|
|
|
+ confval *ret_data; /* allocated structure to add to list */
|
|
|
|
+ int seen = *first != NULL; /* has this option been seen before? */
|
|
|
|
+ char* ptr; /* character that terminated processing in strtox() */
|
|
|
|
+ int err; /* bad numeric value found? */
|
|
|
|
+
|
|
|
|
+ /* if first instance of this option, store result to given addr */
|
|
|
|
+ addr = seen ? NULL : varDesc->addr;
|
|
|
|
+ switch (varDesc->type) {
|
|
|
|
+ case CF_CHAR:
|
|
|
|
+ if (strlen(value) > 1) { /* length 0 is OK; gives null char */
|
|
|
|
+ opterror("a single character", value, varDesc, NULL);
|
|
|
|
+ return(-1);
|
|
|
|
+ }
|
|
|
|
+ data.value.charval = *value;
|
|
|
|
+ if (addr != NULL) {
|
|
|
|
+ *(char*)addr = *value;
|
|
|
|
+ }
|
|
|
|
+ break;
|
|
|
|
+ case CF_STRING:
|
|
|
|
+ case CF_NE_STRING:
|
|
|
|
+ if (varDesc->type == CF_NE_STRING && *value == '\0') {
|
|
|
|
+ opterror("a non-empty string", value, varDesc, NULL);
|
|
|
|
+ return(-1);
|
|
|
|
+ }
|
|
|
|
+ data.value.string = value;
|
|
|
|
+ if (addr != NULL) {
|
|
|
|
+ *(const char**)addr = value;
|
|
|
|
+ }
|
|
|
|
+ break;
|
|
|
|
+ case CF_INT:
|
|
|
|
+ case CF_NON_NEG_INT:
|
|
|
|
+ case CF_POS_INT:
|
|
|
|
+ /* todo: check for out-of-range result */
|
|
|
|
+ errno = 0;
|
|
|
|
+ data.value.intval = strtol(value, &ptr, 0);
|
|
|
|
+ if (errno == ERANGE) {
|
|
|
|
+ opterror("an integer", value, varDesc,
|
|
|
|
+ " (out of range)");
|
|
|
|
+ return(-1);
|
|
|
|
+ }
|
|
|
|
+ err = *value == '\0' || *ptr != '\0';
|
|
|
|
+ switch (varDesc->type) {
|
|
|
|
+ case CF_INT:
|
|
|
|
+ if (err) {
|
|
|
|
+ opterror("an integer", value, varDesc, NULL);
|
|
|
|
+ return(-1);
|
|
|
|
+ }
|
|
|
|
+ break;
|
|
|
|
+ case CF_NON_NEG_INT:
|
|
|
|
+ if (err || data.value.intval < 0) {
|
|
|
|
+ opterror("a non-negative integer", value, varDesc,
|
|
|
|
+ NULL);
|
|
|
|
+ return(-1);
|
|
|
|
+ }
|
|
|
|
+ data.value.nnint = data.value.intval;
|
|
|
|
+ break;
|
|
|
|
+ case CF_POS_INT:
|
|
|
|
+ if (err || data.value.intval <= 0) {
|
|
|
|
+ opterror("a positive integer", value, varDesc, NULL);
|
|
|
|
+ return(-1);
|
|
|
|
+ }
|
|
|
|
+ data.value.nnint = data.value.intval;
|
|
|
|
+ break;
|
|
|
|
+ default:
|
|
|
|
+ /* To avoid complaints from -Wall */
|
|
|
|
+ ;
|
|
|
|
+ }
|
|
|
|
+ if (addr != NULL) {
|
|
|
|
+ *(int*)addr = data.value.intval;
|
|
|
|
+ }
|
|
|
|
+ break;
|
|
|
|
+ case CF_FLOAT:
|
|
|
|
+ case CF_NON_NEG_FLOAT:
|
|
|
|
+ case CF_POS_FLOAT:
|
|
|
|
+ /* todo: check for out-of-range result */
|
|
|
|
+ errno = 0;
|
|
|
|
+ data.value.floatval = strtod(value, &ptr);
|
|
|
|
+ if (errno == ERANGE) {
|
|
|
|
+ opterror("a number", value, varDesc, " (out of range)");
|
|
|
|
+ return(-1);
|
|
|
|
+ }
|
|
|
|
+ err = *value == '\0' || *ptr != '\0';
|
|
|
|
+ switch (varDesc->type) {
|
|
|
|
+ case CF_FLOAT:
|
|
|
|
+ if (err) {
|
|
|
|
+ opterror("a number", value, varDesc, NULL);
|
|
|
|
+ return(-1);
|
|
|
|
+ }
|
|
|
|
+ break;
|
|
|
|
+ case CF_NON_NEG_FLOAT:
|
|
|
|
+ if (err || data.value.floatval < 0) {
|
|
|
|
+ opterror("a non-negative number", value, varDesc,
|
|
|
|
+ NULL);
|
|
|
|
+ return(-1);
|
|
|
|
+ }
|
|
|
|
+ break;
|
|
|
|
+ case CF_POS_FLOAT:
|
|
|
|
+ if (err || data.value.floatval <= 0) {
|
|
|
|
+ opterror("a positive number", value, varDesc, NULL);
|
|
|
|
+ return(-1);
|
|
|
|
+ }
|
|
|
|
+ break;
|
|
|
|
+ default:
|
|
|
|
+ /* To avoid complaints from -Wall */
|
|
|
|
+ ;
|
|
|
|
+ }
|
|
|
|
+ if (addr != NULL) {
|
|
|
|
+ *(double*)addr = data.value.floatval;
|
|
|
|
+ }
|
|
|
|
+ break;
|
|
|
|
+ case CF_SWITCH:
|
|
|
|
+ data.value.switchval = varDesc->value;
|
|
|
|
+ value = "1"; /* for debugging */
|
|
|
|
+ if (addr != NULL) {
|
|
|
|
+ *(int*)addr = varDesc->value;
|
|
|
|
+ }
|
|
|
|
+ break;
|
|
|
|
+ case CF_ENDLIST:
|
|
|
|
+ /* To avoid complaints from -Wall */
|
|
|
|
+ ;
|
|
|
|
+ }
|
|
|
|
+ data.strval = value;
|
|
|
|
+ data.next = NULL;
|
|
|
|
+ if ((ret_data = (confval*)pc_malloc(sizeof(confval))) == NULL) {
|
|
|
|
+ return(-1);
|
|
|
|
+ }
|
|
|
|
+ *ret_data = data;
|
|
|
|
+ if (seen) {
|
|
|
|
+ (*last)->next = ret_data;
|
|
|
|
+ } else {
|
|
|
|
+ *first = ret_data;
|
|
|
|
+ }
|
|
|
|
+ *last = ret_data;
|
|
|
|
+ return(1);
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+/*
|
|
|
|
+ * Input variables:
|
|
|
|
+ * argc, argv: Command line data.
|
|
|
|
+ * optConf[]: Option description structures.
|
|
|
|
+ *
|
|
|
|
+ * Output variables:
|
|
|
|
+ * See addOptVal().
|
|
|
|
+ * After processing, argc will be the number of non-option arguments and argv
|
|
|
|
+ * will start with the first non-option argument.
|
|
|
|
+ *
|
|
|
|
+ * Return value:
|
|
|
|
+ * On success, the number of options processed.
|
|
|
|
+ * On error, a string describing the error is stored in the global errmsg and
|
|
|
|
+ * -1 is returned.
|
|
|
|
+ */
|
|
|
|
+static int
|
|
|
|
+procCmdLineArgs(int* argc, const char** argv[], const confvar_t optConf[],
|
|
|
|
+ confval** perOptRecordsFirst, confval** perOptRecordsLast) {
|
|
|
|
+ char* p;
|
|
|
|
+ extern char* optarg; /* For getopt */
|
|
|
|
+ extern int optind; /* For getopt */
|
|
|
|
+ extern int optopt; /* For getopt */
|
|
|
|
+ char optstr[514]; /* List of option chars, for getopt */
|
|
|
|
+ unsigned optCharToConf[256]; /* Map option char/num to confvar */
|
|
|
|
+ int optchar; /* value returned by getopt() */
|
|
|
|
+ int confNum; /* to iterate over confvars */
|
|
|
|
+ int count = 0; /* number of options processed */
|
|
|
|
+
|
|
|
|
+ p = optstr;
|
|
|
|
+ *(p++) = ':';
|
|
|
|
+ for (confNum = 0; optConf[confNum].type != CF_ENDLIST; confNum++) {
|
|
|
|
+ unsigned outind = optConf[confNum].outind;
|
|
|
|
+ if (outind < 256 && isprint(outind)) {
|
|
|
|
+ *(p++) = (char)outind;
|
|
|
|
+ switch (optConf[confNum].type) {
|
|
|
|
+ case CF_SWITCH:
|
|
|
|
+ break;
|
|
|
|
+ default:
|
|
|
|
+ *(p++) = ':';
|
|
|
|
+ break;
|
|
|
|
+ }
|
|
|
|
+ optCharToConf[outind] = confNum;
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ *p = '\0';
|
|
|
|
+ optind = 1;
|
|
|
|
+ while ((optchar = getopt(*argc, *argv, optstr)) != -1)
|
|
|
|
+ {
|
|
|
|
+ int ind;
|
|
|
|
+ int ret;
|
|
|
|
+
|
|
|
|
+ if (optchar == '?') {
|
|
|
|
+ error(USAGE_ERROR, "Unknown option character '%c'", optopt);
|
|
|
|
+ return(-1);
|
|
|
|
+ } else if (optchar == ':') {
|
|
|
|
+ error(USAGE_ERROR, "No value given for option -%c", optopt);
|
|
|
|
+ return(-1);
|
|
|
|
+ }
|
|
|
|
+ ind = optCharToConf[optchar];
|
|
|
|
+ switch (ret = addOptVal(optarg, &optConf[ind],
|
|
|
|
+ &perOptRecordsFirst[ind],
|
|
|
|
+ &perOptRecordsLast[ind])) {
|
|
|
|
+ case 1:
|
|
|
|
+ count++;
|
|
|
|
+ break;
|
|
|
|
+ case 0:
|
|
|
|
+ break;
|
|
|
|
+ default:
|
|
|
|
+ return(ret);
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ *argc -= optind;
|
|
|
|
+ *argv += optind;
|
|
|
|
+ return(count);
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+/*
|
|
|
|
+ * Input variables:
|
|
|
|
+ * argc, argv: Command line data.
|
|
|
|
+ * optConf[]: Option description structures.
|
|
|
|
+ * name: Name of program, for messages.
|
|
|
|
+ * usage: Usage message. If non-null, on error a message is printed to stderr
|
|
|
|
+ * and the program exits.
|
|
|
|
+ *
|
|
|
|
+ * Output variables:
|
|
|
|
+ * Option values are stored at the value given by any confvar that has a
|
|
|
|
+ * non-null address.
|
|
|
|
+ * If confdatda is not null, the processed option values are stored in
|
|
|
|
+ * confdata.
|
|
|
|
+ * A pointer to the start of the values for each option is stored in
|
|
|
|
+ * confdata->optVals[].values at the same offset as the option appears in
|
|
|
|
+ * confdata[].
|
|
|
|
+ * For any option for option characters/indexes that have been used,
|
|
|
|
+ * confdata->map[index] is set to the same data.
|
|
|
|
+ * After processing, argc will have been adjusted to be the number of
|
|
|
|
+ * non-option arguments and argv will have been adjusted to start with the
|
|
|
|
+ * first non-option argument.
|
|
|
|
+ * The malloced data structures returned in confdata are:
|
|
|
|
+ * optVals
|
|
|
|
+ * optVals[0].values
|
|
|
|
+ * If any option characters/indexes are used, map. If not used, this will be
|
|
|
|
+ * a null pointer.
|
|
|
|
+ *
|
|
|
|
+ * Return value:
|
|
|
|
+ * On success, NULL.
|
|
|
|
+ * On error, a message describing the problem.
|
|
|
|
+ */
|
|
|
|
+const char*
|
|
|
|
+procOpts(int* argc, const char** argv[], const confvar_t optConf[],
|
|
|
|
+ confdata_t* confdata, const char name[],
|
|
|
|
+ const char usage[]) {
|
|
|
|
+ /*
|
|
|
|
+ * First & last records in the linked lists maintained for each option.
|
|
|
|
+ * These will point to arrays indexed by option number, giving one pointer
|
|
|
|
+ * (each) per option, used to maintain/return the list of values seen for
|
|
|
|
+ * that option (see the description of first & last in addOptVal()
|
|
|
|
+ */
|
|
|
|
+ confval** perOptRecordsFirst;
|
|
|
|
+ confval** perOptRecordsLast;
|
|
|
|
+
|
|
|
|
+ /* Number of configuration options given in optConf */
|
|
|
|
+ unsigned numConf;
|
|
|
|
+ unsigned maxOptIndex = 0; /* The highest option index number seen */
|
|
|
|
+ /* number of option instances + assignments given */
|
|
|
|
+ int numOptsFound;
|
|
|
|
+ int optNum; /* to iterate through the possible options */
|
|
|
|
+ int i; /* index into the global list of option value structures */
|
|
|
|
+ confval** valuePointers; /* global list of value structures */
|
|
|
|
+
|
|
|
|
+ pc_name = name;
|
|
|
|
+ pc_usage = usage;
|
|
|
|
+ for (numConf = 0; optConf[numConf].type != CF_ENDLIST; numConf++) {
|
|
|
|
+ unsigned outind = optConf[numConf].outind;
|
|
|
|
+
|
|
|
|
+ if ((outind & ~CF_NOTFLAG) > maxOptIndex) {
|
|
|
|
+ maxOptIndex = outind & ~CF_NOTFLAG;
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ if (numConf == 0) {
|
|
|
|
+ error(INTERNAL_ERROR, "Empty confvar list");
|
|
|
|
+ return(errmsg);
|
|
|
|
+ }
|
|
|
|
+ if ((perOptRecordsFirst = (confval**)pc_malloc(sizeof(confval*) * numConf))
|
|
|
|
+ == NULL || (perOptRecordsLast =
|
|
|
|
+ (confval**)pc_malloc(sizeof(confval*) * numConf)) == NULL) {
|
|
|
|
+ return(errmsg);
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ numOptsFound = procCmdLineArgs(argc, argv, optConf, perOptRecordsFirst,
|
|
|
|
+ perOptRecordsLast);
|
|
|
|
+ free(perOptRecordsLast);
|
|
|
|
+ perOptRecordsLast = NULL;
|
|
|
|
+
|
|
|
|
+ if (numOptsFound < 0)
|
|
|
|
+ {
|
|
|
|
+ free(perOptRecordsFirst);
|
|
|
|
+ return(errmsg);
|
|
|
|
+ }
|
|
|
|
+ if (confdata == NULL) {
|
|
|
|
+ free(perOptRecordsFirst);
|
|
|
|
+ return NULL;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ /*
|
|
|
|
+ * All options have been read & initial processing done.
|
|
|
|
+ * An array of pointers is now generated for the options.
|
|
|
|
+ */
|
|
|
|
+ if ((valuePointers =
|
|
|
|
+ (confval**)pc_malloc(sizeof(confval*) * numOptsFound)) == NULL ||
|
|
|
|
+ (confdata->optVals =
|
|
|
|
+ (cf_option*)pc_malloc(sizeof(cf_option) * numConf)) == NULL) {
|
|
|
|
+ return(errmsg);
|
|
|
|
+ }
|
|
|
|
+ /* If option characters / indexes are used, allocate a map for them */
|
|
|
|
+ if (maxOptIndex == 0) {
|
|
|
|
+ confdata->map = NULL;
|
|
|
|
+ } else {
|
|
|
|
+ if ((confdata->map = (cf_option**)pc_malloc(sizeof(cf_option) *
|
|
|
|
+ (maxOptIndex+1))) == NULL) {
|
|
|
|
+ return(errmsg);
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ /*
|
|
|
|
+ * Store the linked lists of option values into arrays.
|
|
|
|
+ * Pointers to all option instances are stored in valuePointers,
|
|
|
|
+ * with the values for each particular option being contiguous.
|
|
|
|
+ */
|
|
|
|
+ i = 0;
|
|
|
|
+ for (optNum = 0; optNum < numConf; optNum++) {
|
|
|
|
+ unsigned outind = optConf[optNum].outind;
|
|
|
|
+ confval* optval;
|
|
|
|
+
|
|
|
|
+ confdata->optVals[optNum].num = 0;
|
|
|
|
+ confdata->optVals[optNum].values = &valuePointers[i];
|
|
|
|
+ if (outind != 0) {
|
|
|
|
+ confdata->map[outind & ~CF_NOTFLAG] = &confdata->optVals[optNum];
|
|
|
|
+ }
|
|
|
|
+ for (optval = perOptRecordsFirst[optNum]; optval != NULL;
|
|
|
|
+ optval = optval->next) {
|
|
|
|
+ confdata->optVals[optNum].num++;
|
|
|
|
+ valuePointers[i++] = optval;
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ free(perOptRecordsFirst);
|
|
|
|
+ return(NULL);
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+/*
|
|
|
|
+ * Free the malloced data stored in confdata elements by ProcOpts()
|
|
|
|
+ */
|
|
|
|
+void
|
|
|
|
+confdataFree(confdata_t *confdata) {
|
|
|
|
+ if (confdata->map != NULL) {
|
|
|
|
+ free(confdata->map);
|
|
|
|
+ confdata->map = NULL;
|
|
|
|
+ }
|
|
|
|
+ free(confdata->optVals[0].values);
|
|
|
|
+ free(confdata->optVals);
|
|
|
|
+ confdata->optVals = NULL;
|
|
|
|
+}
|