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