123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485 |
- /*
- * 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;
- }
|