#define _XOPEN_SOURCE 600 /* Make sure has strdup() */ #include "xmlrpc_config.h" /* prereq for mallocvar.h -- defines __inline__ */ #include #include #include #include #include #include "bool.h" #include "int.h" #include "mallocvar.h" #include "casprintf.h" #include "getoptx.h" #include "string_parser.h" #include "cmdline_parser.h" #define MAXOPTS 100 struct optionDesc { const char * name; enum optiontype type; bool present; union { unsigned int u; int i; const char * s; uint64_t llu; double d; } value; }; struct cmdlineParserCtl { struct optionDesc * optionDescArray; unsigned int numOptions; const char ** argumentArray; unsigned int numArguments; }; static struct optionx * createLongOptsArray(struct optionDesc * const optionDescArray, unsigned int const numOptions) { struct optionx * longopts; MALLOCARRAY(longopts, numOptions+1); if (longopts != NULL) { unsigned int i; for (i = 0; i < numOptions; ++i) { longopts[i].name = optionDescArray[i].name; /* If the option takes a value, we say it is optional even though it never is. That's because if we say it is mandatory, getopt_long_only() pretends it doesn't even recognize the option if the user doesn't give a value. We prefer to generate a meaningful error message when the user omits a required option value. */ longopts[i].has_arg = optionDescArray[i].type == OPTTYPE_FLAG ? no_argument : optional_argument; longopts[i].flag = NULL; longopts[i].val = i; } longopts[numOptions].name = 0; longopts[numOptions].has_arg = 0; longopts[numOptions].flag = 0; longopts[numOptions].val = 0; } return longopts; } static void parseInt(enum optiontype const type, const char * const optarg, unsigned int * const valueUintP, int * const valueIntP, const char ** const errorP) { if (optarg == NULL) casprintf(errorP, "Option requires a value"); else if (strlen(optarg) == 0) casprintf(errorP, "Numeric option value is null string"); else { char * tailptr; long const longvalue = strtol(optarg, &tailptr, 10); if (*tailptr != '\0') casprintf(errorP, "Non-numeric value " "for numeric option value: '%s'", optarg); else if (errno == ERANGE || longvalue > INT_MAX) casprintf(errorP, "Numeric value out of range: %s", optarg); else { if (type == OPTTYPE_UINT) { if (longvalue < 0) casprintf(errorP, "Unsigned numeric value is " "negative: %ld", longvalue); else { *errorP = NULL; *valueUintP = (unsigned int) longvalue; } } else { *errorP = NULL; *valueIntP = (int) longvalue; } } } } static void parseBinUint(const char * const optarg, uint64_t * const valueP, const char ** const errorP) { if (optarg == NULL) casprintf(errorP, "Option requires a value"); else if (strlen(optarg) == 0) casprintf(errorP, "Numeric option value is null string"); else { const char * error; interpretBinUint(optarg, valueP, &error); if (error) { casprintf(errorP, "Invalid numeric option value '%s'. %s", optarg, error); strfree(error); } } } static void parseFloat(const char * const optarg, double * const valueP, const char ** const errorP) { if (optarg == NULL) casprintf(errorP, "Option requires a value"); else if (strlen(optarg) == 0) casprintf(errorP, "Numeric option value is null string"); else { char * tailptr; double const doublevalue = strtod(optarg, &tailptr); if (*tailptr != '\0') casprintf(errorP, "Non-numeric value " "for numeric option value: '%s'", optarg); else if (errno == ERANGE) casprintf(errorP, "Numeric value out of range: %s", optarg); else { *errorP = NULL; *valueP = doublevalue; } } } static void parseOptionValue(const char * const optarg, struct optionDesc * const optionP, const char ** const errorP) { switch (optionP->type) { case OPTTYPE_FLAG: *errorP = NULL; break; case OPTTYPE_INT: case OPTTYPE_UINT: parseInt(optionP->type, optarg, &optionP->value.u, &optionP->value.i, errorP); break; case OPTTYPE_STRING: if (optarg == NULL) casprintf(errorP, "Option requires a value"); else { *errorP = NULL; optionP->value.s = strdup(optarg); } break; case OPTTYPE_BINUINT: parseBinUint(optarg, &optionP->value.llu, errorP); break; case OPTTYPE_FLOAT: parseFloat(optarg, &optionP->value.d, errorP); break; } } static void processOption(struct optionDesc * const optionP, const char * const optarg, const char ** const errorP) { const char * error; parseOptionValue(optarg, optionP, &error); if (error) casprintf(errorP, "Error in '%s' option: %s", optionP->name, error); else optionP->present = true; } static void extractArguments(struct cmdlineParserCtl * const cpP, unsigned int const argc, const char ** const argv) { cpP->numArguments = argc - getopt_argstart(); MALLOCARRAY(cpP->argumentArray, cpP->numArguments); if (cpP->argumentArray == NULL) { fprintf(stderr, "Unable to allocate memory for argument array " "(%u arguments)\n", cpP->numArguments); abort(); } else { unsigned int i; for (i = 0; i < cpP->numArguments; ++i) { cpP->argumentArray[i] = strdup(argv[getopt_argstart() + i]); if (cpP->argumentArray[i] == NULL) { fprintf(stderr, "Unable to allocate memory for Argument %u\n", i); abort(); } } } } void cmd_processOptions(cmdlineParser const cpP, int const argc, const char ** const argv, const char ** const errorP) { struct optionx * longopts; longopts = createLongOptsArray(cpP->optionDescArray, cpP->numOptions); if (longopts == NULL) casprintf(errorP, "Unable to get memory for longopts array"); else { int endOfOptions; unsigned int i; *errorP = NULL; /* Set up initial assumption: No options present */ for (i = 0; i < cpP->numOptions; ++i) cpP->optionDescArray[i].present = false; endOfOptions = false; /* initial value */ while (!endOfOptions && !*errorP) { int const opterr0 = 0; /* Don't let getopt_long_only() print an error message */ unsigned int longoptsIndex; const char * unrecognizedOption; const char * optarg; getopt_long_onlyx(argc, (char**) argv, "", longopts, &longoptsIndex, opterr0, &endOfOptions, &optarg, &unrecognizedOption); if (unrecognizedOption) casprintf(errorP, "Unrecognized option: '%s'", unrecognizedOption); else { if (!endOfOptions) processOption(&cpP->optionDescArray[longoptsIndex], optarg, errorP); } } if (!*errorP) extractArguments(cpP, argc, argv); free(longopts); } } cmdlineParser cmd_createOptionParser(void) { struct cmdlineParserCtl * cpP; MALLOCVAR(cpP); if (cpP != NULL) { struct optionDesc * optionDescArray; cpP->numOptions = 0; MALLOCARRAY(optionDescArray, MAXOPTS); if (optionDescArray == NULL) { free(cpP); cpP = NULL; } else cpP->optionDescArray = optionDescArray; } return cpP; } void cmd_destroyOptionParser(cmdlineParser const cpP) { unsigned int i; for (i = 0; i < cpP->numOptions; ++i) { struct optionDesc const option = cpP->optionDescArray[i]; if (option.type == OPTTYPE_STRING && option.present) strfree(option.value.s); strfree(option.name); } for (i = 0; i < cpP->numArguments; ++i) strfree(cpP->argumentArray[i]); free(cpP->optionDescArray); free(cpP); } void cmd_defineOption(cmdlineParser const cpP, const char * const name, enum optiontype const type) { if (cpP->numOptions < MAXOPTS) { cpP->optionDescArray[cpP->numOptions].name = strdup(name); cpP->optionDescArray[cpP->numOptions].type = type; ++cpP->numOptions; } } static struct optionDesc * findOptionDesc(struct cmdlineParserCtl * const cpP, const char * const name) { struct optionDesc * retval; unsigned int i; retval = NULL; for (i = 0; i < cpP->numOptions && !retval; ++i) if (strcmp(cpP->optionDescArray[i].name, name) == 0) retval = &cpP->optionDescArray[i]; return retval; } int cmd_optionIsPresent(cmdlineParser const cpP, const char * const name) { struct optionDesc * const optionDescP = findOptionDesc(cpP, name); bool present; if (!optionDescP) { fprintf(stderr, "cmdlineParser called incorrectly. " "optionIsPresent() called for undefined option '%s'\n", name); abort(); } else present = optionDescP->present; return present; } unsigned int cmd_getOptionValueUint(cmdlineParser const cpP, const char * const name) { struct optionDesc * const optionDescP = findOptionDesc(cpP, name); unsigned int retval; if (!optionDescP) { fprintf(stderr, "cmdlineParser called incorrectly. " "cmd_getOptionValueUint() called for undefined option '%s'\n", name); abort(); } else { if (optionDescP->type != OPTTYPE_UINT) { fprintf(stderr, "cmdlineParser called incorrectly. " "cmd_getOptionValueUint() called for non-unsigned integer " "option '%s'\n", optionDescP->name); abort(); } else { if (optionDescP->present) retval = optionDescP->value.u; else retval = 0; } } return retval; } int cmd_getOptionValueInt(cmdlineParser const cpP, const char * const name) { struct optionDesc * const optionDescP = findOptionDesc(cpP, name); int retval; if (!optionDescP) { fprintf(stderr, "cmdlineParser called incorrectly. " "cmd_getOptionValueInt() called for undefined option '%s'\n", name); abort(); } else { if (optionDescP->type != OPTTYPE_INT) { fprintf(stderr, "cmdlineParser called incorrectly. " "cmd_getOptionValueInt() called for non-integer " "option '%s'\n", optionDescP->name); abort(); } else { if (optionDescP->present) retval = optionDescP->value.i; else retval = 0; } } return retval; } const char * cmd_getOptionValueString(cmdlineParser const cpP, const char * const name) { struct optionDesc * const optionDescP = findOptionDesc(cpP, name); const char * retval; if (!optionDescP) { fprintf(stderr, "cmdlineParser called incorrectly. " "cmd_getOptionValueString() called for " "undefined option '%s'\n", name); abort(); } else { if (optionDescP->type != OPTTYPE_STRING) { fprintf(stderr, "cmdlineParser called incorrectly. " "getOptionValueString() called for non-string " "option '%s'\n", optionDescP->name); abort(); } else { if (optionDescP->present) { retval = strdup(optionDescP->value.s); if (retval == NULL) { fprintf(stderr, "out of memory in cmd_getOptionValueString()\n"); abort(); } } else retval = NULL; } } return retval; } uint64_t cmd_getOptionValueBinUint(cmdlineParser const cpP, const char * const name) { struct optionDesc * const optionDescP = findOptionDesc(cpP, name); uint64_t retval; if (!optionDescP) { fprintf(stderr, "cmdlineParser called incorrectly. " "cmd_getOptionValueUint() called for undefined option '%s'\n", name); abort(); } else { if (optionDescP->type != OPTTYPE_BINUINT) { fprintf(stderr, "cmdlineParser called incorrectly. " "cmd_getOptionValueBinUint() called for " "non-OPTTYPE_BINUINT " "option '%s'\n", optionDescP->name); abort(); } else { if (optionDescP->present) retval = optionDescP->value.llu; else retval = 0; } } return retval; } double cmd_getOptionValueFloat(cmdlineParser const cpP, const char * const name) { struct optionDesc * const optionDescP = findOptionDesc(cpP, name); double retval; if (!optionDescP) { fprintf(stderr, "cmdlineParser called incorrectly. " "cmd_getOptionValueInt() called for undefined option '%s'\n", name); abort(); } else { if (optionDescP->type != OPTTYPE_FLOAT) { fprintf(stderr, "cmdlineParser called incorrectly. " "cmd_getOptionValueInt() called for non-float " "option '%s'\n", optionDescP->name); abort(); } else { if (optionDescP->present) retval = optionDescP->value.d; else retval = 0.0; } } return retval; } unsigned int cmd_argumentCount(cmdlineParser const cpP) { return cpP->numArguments; } const char * cmd_getArgument(cmdlineParser const cpP, unsigned int const argNumber) { const char * retval; if (argNumber >= cpP->numArguments) retval = NULL; else { retval = strdup(cpP->argumentArray[argNumber]); if (retval == NULL) { fprintf(stderr, "out of memory in cmd_getArgument()\n"); abort(); } } return retval; }