Index: ossp-pkg/petidomo/libargv/argv.c RCS File: /v/ossp/cvs/ossp-pkg/petidomo/libargv/argv.c,v co -q -kk -p'1.1.1.1' '/v/ossp/cvs/ossp-pkg/petidomo/libargv/argv.c,v' | diff -u /dev/null - -L'ossp-pkg/petidomo/libargv/argv.c' 2>/dev/null --- ossp-pkg/petidomo/libargv/argv.c +++ - 2025-09-10 00:36:38.055674281 +0200 @@ -0,0 +1,2475 @@ +/* + * Generic argv processor... + * + * Copyright 1995 by Gray Watson + * + * This file is part of the argv library. + * + * Permission to use, copy, modify, and distribute this software for + * any purpose and without fee is hereby granted, provided that the + * above copyright notice and this permission notice appear in all + * copies, and that the name of Gray Watson not be used in advertising + * or publicity pertaining to distribution of the document or software + * without specific, written prior permission. + * + * Gray Watson makes no representations about the suitability of the + * software described herein for any purpose. It is provided "as is" + * without express or implied warranty. + * + * The author may be contacted at gray.watson@letters.com + * + * - Parse command line parameters conveniently. + * + */ + +#include +#include +#include +#include +#include + +#include "_argv.h" + +/* internal routines */ +LOCAL void do_list(argv_t *grid, const int argc, char **argv, + char *okay_p); + +/* + * exported variables + */ +/* This is a processed version of argv[0], pre-path removed: /bin/ls -> ls */ +EXPORT char argv_program[PROGRAM_NAME + 1] = "Unknown"; + +/* A global value of argv from main after argv_process has been called */ +EXPORT char **argv_argv = NULL; +/* A global value of argc from main after argv_process has been called */ +EXPORT int argv_argc = 0; + +/* This should be set externally to provide general program help to user */ +EXPORT char *argv_help_string = NULL; +/* This should be set externally to provide version information to the user */ +EXPORT char *argv_version_string = NULL; + +/* + * Are we running interactively? This will exit on errors. Set to + * false to return error codes instead. + */ +EXPORT char argv_interactive = ARGV_TRUE; + +/* + * The FILE stream that argv out_puts all its errors. Set to NULL to + * not dump any error messages. Default is stderr. + */ +EXPORT FILE *argv_error_stream = ERROR_STREAM_INIT; + +/* local variables */ +LOCAL_QUEUE_DECLARE(argv_t *); /* args waiting for values */ +LOCAL argv_t empty[] = {{ ARGV_LAST }}; /* empty argument array */ +LOCAL char enabled = ARGV_FALSE; /* are the lights on? */ + +/* global settings */ +LOCAL int global_close = GLOBAL_CLOSE_ENABLE; /* close processing */ +LOCAL int global_env = GLOBAL_ENV_BEFORE; /* env processing */ +LOCAL int global_error = GLOBAL_ERROR_SEE; /* error processing */ +LOCAL int global_multi = GLOBAL_MULTI_ACCEPT; /* multi processing */ +LOCAL int global_usage = GLOBAL_USAGE_LONG; /* usage processing */ + +/****************************** startup routine ******************************/ + +/* + * Turn on the lights. + */ +LOCAL void argv_startup(void) +{ + if (enabled) + return; + enabled = ARGV_TRUE; + + /* ANSI says we cannot predefine this above */ + if (argv_error_stream == ERROR_STREAM_INIT) + argv_error_stream = stderr; +} + +/***************************** general utilities *****************************/ + +/* + * Binary STR to integer translation + */ +LOCAL int btoi(const char *str) +{ + int ret = 0; + + /* strip off spaces */ + for (; isspace((int)*str); str++); + + for (; *str == '0' || *str == '1'; str++) { + ret *= 2; + ret += *str - '0'; + } + + return ret; +} + +/* + * Octal STR to integer translation + */ +LOCAL int otoi(const char *str) +{ + int ret = 0; + + /* strip off spaces */ + for (; isspace((int)*str); str++); + + for (; *str >= '0' && *str <= '7'; str++) { + ret *= 8; + ret += *str - '0'; + } + + return ret; +} + +/* + * Hexadecimal STR to integer translation + */ +LOCAL int htoi(const char *str) +{ + int ret = 0; + + /* strip off spaces */ + for (; isspace((int)*str); str++); + + /* skip a leading 0[xX] */ + if (*str == '0' && (*(str + 1) == 'x' || *(str + 1) == 'X')) + str += 2; + + for (; isdigit((int)*str) || + (*str >= 'a' && *str <= 'f') || (*str >= 'A' && *str <= 'F'); + str++) { + ret *= 16; + if (*str >= 'a' && *str <= 'f') + ret += *str - 'a' + 10; + else if (*str >= 'A' && *str <= 'F') + ret += *str - 'A' + 10; + else + ret += *str - '0'; + } + + return ret; +} + +/* + * Basically a strdup for compatibility sake + */ +LOCAL char *string_copy(const char *ptr) +{ + const char *ptr_p; + char *ret, *ret_p; + int len; + + len = strlen(ptr); + ret = (char *)malloc(len + 1); + if (ret == NULL) { + if (argv_error_stream != NULL) + (void)fprintf(argv_error_stream, + "%s: memory error during argument processing\n", + argv_program); + if (argv_interactive) + (void)exit(EXIT_CODE); + return NULL; + } + + for (ptr_p = ptr, ret_p = ret; *ptr_p != NULLC;) + *ret_p++ = *ptr_p++; + *ret_p = NULLC; + + return ret; +} + +/* + * Break STR and return an array of char * whose values are tokenized + * by TOK. it passes back the number of tokens in TOKN. + * + * NOTE: the return value should be freed later and the STR should stay + * around until that time. + */ +LOCAL char **vectorize(char *str, const char *tok, int *tokn) +{ + char **vect_p; + char *tmp, *tok_p; + int tok_c; + + /* count the tokens */ + tmp = string_copy(str); + if (tmp == NULL) + return NULL; + + tok_p = strtok(tmp, tok); + for (tok_c = 0; tok_p != NULL; tok_c++) + tok_p = strtok(NULL, tok); + free(tmp); + + *tokn = tok_c; + + if (tok_c == 0) + return NULL; + + /* allocate the pointer grid */ + vect_p = (char **)malloc(sizeof(char *) * tok_c); + if (vect_p == NULL) { + if (argv_error_stream != NULL) + (void)fprintf(argv_error_stream, + "%s: memory error during argument processing\n", + argv_program); + if (argv_interactive) + (void)exit(EXIT_CODE); + return NULL; + } + + /* load the tokens into the list */ + vect_p[0] = strtok(str, tok); + + for (tok_c = 1; tok_c < *tokn; tok_c++) + vect_p[tok_c] = strtok(NULL, tok); + + return vect_p; +} + +/* + * Display printable chars from BUF of SIZE, non-printables as \%03o + */ +LOCAL char *expand_buf(const void *buf, const int size) +{ + static char out[DUMP_SPACE_BUF]; + int size_c; + void *buf_p; + char *out_p; + + for (size_c = 0, out_p = out, buf_p = (void *)buf; size_c < size; + size_c++, buf_p = (char *)buf_p + 1) { + char *spec_p; + + /* handle special chars */ + if (out_p + 2 >= out + sizeof(out)) + break; + + /* search for special characters */ + for (spec_p = SPECIAL_CHARS + 1; *(spec_p - 1) != NULLC; spec_p += 2) + if (*spec_p == *(char *)buf_p) + break; + + /* did we find one? */ + if (*(spec_p - 1) != NULLC) { + if (out_p + 2 >= out + sizeof(out)) + break; + (void)sprintf(out_p, "\\%c", *(spec_p - 1)); + out_p += 2; + continue; + } + + if (*(unsigned char *)buf_p < 128 && isprint((int)(*(char *)buf_p))) { + if (out_p + 1 >= out + sizeof(out)) + break; + *out_p = *(char *)buf_p; + out_p += 1; + } + else { + if (out_p + 4 >= out + sizeof(out)) + break; + (void)sprintf(out_p, "\\%03o", *(unsigned char *)buf_p); + out_p += 4; + } + } + + *out_p = NULLC; + return out; +} + +/****************************** usage routines *******************************/ + +/* + * Print a short-format usage message. + */ +LOCAL void usage_short(const argv_t *args, const int flag) +{ + const argv_t *arg_p; + int len, col_c = 0; + char mark = ARGV_FALSE, *prefix; + + if (argv_error_stream == NULL) + return; + + /* print the usage message header */ + (void)fprintf(argv_error_stream, "%s%s", USAGE_LABEL, argv_program); + col_c += USAGE_LABEL_LENGTH + strlen(argv_program); + + /* + * print all of the boolean arguments first. + * NOTE: we assume they all fit on the line + */ + for (arg_p = args; arg_p->ar_short_arg != ARGV_LAST; arg_p++) { + + /* skip or-specifiers */ + if (arg_p->ar_short_arg == ARGV_OR + || arg_p->ar_short_arg == ARGV_XOR) + continue; + + /* skip non booleans */ + if (HAS_ARG(arg_p->ar_type)) + continue; + + /* skip args with no short component */ + if (arg_p->ar_short_arg == NULLC) + continue; + + if (! mark) { + len = 2 + SHORT_PREFIX_LENGTH; + prefix = " ["; + + /* we check for -2 here because we should have 1 arg and ] on line */ + if (col_c + len > SCREEN_WIDTH - 2) { + (void)fprintf(argv_error_stream, "\n%*.*s", + (int)USAGE_LABEL_LENGTH, (int)USAGE_LABEL_LENGTH, ""); + col_c = USAGE_LABEL_LENGTH; + + /* if we are the start of a line, skip any starting spaces */ + if (*prefix == ' ') { + prefix++; + len--; + } + } + + (void)fprintf(argv_error_stream, "%s%s", prefix, SHORT_PREFIX); + col_c += len; + mark = ARGV_TRUE; + } + + len = 1; + /* we check for -1 here because we should need ] */ + if (col_c + len > SCREEN_WIDTH - 1) { + (void)fprintf(argv_error_stream, "]\n%*.*s", + (int)USAGE_LABEL_LENGTH, (int)USAGE_LABEL_LENGTH, ""); + col_c = USAGE_LABEL_LENGTH; + + /* restart the short option list */ + (void)fprintf(argv_error_stream, "[%s", SHORT_PREFIX); + col_c += 1 + SHORT_PREFIX_LENGTH; + } + + (void)fprintf(argv_error_stream, "%c", arg_p->ar_short_arg); + col_c++; + } + + if (mark) { + (void)fprintf(argv_error_stream, "]"); + col_c++; + } + + /* print remaining (non-boolean) arguments */ + for (arg_p = args; arg_p->ar_short_arg != ARGV_LAST; arg_p++) { + int var_len; + char *var_str, *postfix; + + /* skip or-specifiers */ + if (arg_p->ar_short_arg == ARGV_OR + || arg_p->ar_short_arg == ARGV_XOR) + continue; + + /* skip booleans types */ + if (! HAS_ARG(arg_p->ar_type)) + continue; + + if (arg_p->ar_var_label == NULL) { + if (ARGV_TYPE(arg_p->ar_type) == ARGV_BOOL_ARG) { + var_str = BOOL_ARG_LABEL; + var_len = BOOL_ARG_LENGTH; + } + else { + var_len = UNKNOWN_ARG_LENGTH; + var_str = UNKNOWN_ARG; + } + } + else { + var_len = strlen(arg_p->ar_var_label); + var_str = arg_p->ar_var_label; + } + + if (arg_p->ar_short_arg == ARGV_MAND) { + /* print the mandatory argument desc */ + len = 1 + var_len; + prefix = " "; + postfix = ""; + } + else if (arg_p->ar_short_arg == ARGV_MAYBE) { + /* print the maybe argument desc */ + len = 2 + var_len + 1; + prefix = " ["; + postfix = "]"; + } + else { + /* handle options with arguments */ + + /* " [" + short_prefix + char */ + len = 2 + SHORT_PREFIX_LENGTH + 1; + prefix = " ["; + + /* do we need to wrap */ + if (col_c + len > SCREEN_WIDTH) { + (void)fprintf(argv_error_stream, "\n%*.*s", + (int)USAGE_LABEL_LENGTH, (int)USAGE_LABEL_LENGTH, ""); + col_c = USAGE_LABEL_LENGTH; + + /* if we are the start of a line, skip any starting spaces */ + if (*prefix == ' ') { + prefix++; + len--; + } + } + (void)fprintf(argv_error_stream, "%s%s%c", + prefix, SHORT_PREFIX, arg_p->ar_short_arg); + col_c += len; + + len = 1 + var_len + 1; + prefix = " "; + postfix = "]"; + } + + if (col_c + len > SCREEN_WIDTH) { + (void)fprintf(argv_error_stream, "\n%*.*s", + (int)USAGE_LABEL_LENGTH, (int)USAGE_LABEL_LENGTH, ""); + col_c = USAGE_LABEL_LENGTH; + + /* if we are the start of a line, skip any starting spaces */ + if (*prefix == ' ') { + prefix++; + len--; + } + } + + (void)fprintf(argv_error_stream, "%s%s%s", prefix, var_str, postfix); + col_c += len; + } + + (void)fprintf(argv_error_stream, "\n"); + + if (flag == GLOBAL_USAGE_SHORTREM) + (void)fprintf(argv_error_stream, + "%*.*sUse the '%s%s' argument for more assistance.\n", + (int)USAGE_LABEL_LENGTH, (int)USAGE_LABEL_LENGTH, "", + LONG_PREFIX, USAGE_ARG); +} + +/* + * Display an argument type while keeping track of COL_C. + */ +LOCAL void display_arg(FILE *stream, const argv_t *arg_p, const int max, + int *col_c) +{ + int var_len, len; + + if (arg_p->ar_var_label == NULL) + var_len = 0; + else + var_len = strlen(arg_p->ar_var_label); + + switch (ARGV_TYPE(arg_p->ar_type)) { + + case ARGV_BOOL: + case ARGV_BOOL_NEG: + case ARGV_INCR: + break; + + case ARGV_BOOL_ARG: + (void)fprintf(stream, "%s", BOOL_ARG_LABEL); + (*col_c) += BOOL_ARG_LENGTH; + break; + + case ARGV_CHAR: + case ARGV_CHARP: + case ARGV_FLOAT: + case ARGV_SHORT: + case ARGV_INT: + case ARGV_U_INT: + case ARGV_LONG: + case ARGV_U_LONG: + case ARGV_BIN: + case ARGV_OCT: + case ARGV_HEX: + if (arg_p->ar_var_label == NULL) { + len = max - *col_c; + (void)fprintf(stream, "%-.*s", len, UNKNOWN_ARG); + *col_c += MIN(len, UNKNOWN_ARG_LENGTH); + } + else { + len = max - *col_c; + (void)fprintf(stream, "%-.*s", len, arg_p->ar_var_label); + *col_c += MIN(len, var_len); + } + break; + } +} + +/* + * Display an option entry ARG_P to STREAM while counting COL_C. + */ +LOCAL void display_option(FILE *stream, const argv_t *arg_p, int *col_c) +{ + if (stream == NULL) + return; + + (void)fputc('[', stream); + (*col_c)++; + + /* arg maybe does not have a -? preface */ + if (arg_p->ar_short_arg != ARGV_MAYBE) { + (void)fprintf(stream, "%s%c", + SHORT_PREFIX, arg_p->ar_short_arg); + *col_c += SHORT_PREFIX_LENGTH + 1; + + if (HAS_ARG(arg_p->ar_type)) { + /* display optional argument */ + (void)fputc(' ', stream); + (*col_c)++; + } + } + + display_arg(stream, arg_p, LONG_COLUMN - 1, col_c); + (void)fputc(']', stream); + (*col_c)++; +} + +/* + * Print a long-format usage message. + */ +LOCAL void usage_long(const argv_t *args) +{ + const argv_t *arg_p; + int col_c, len; + + if (argv_error_stream == NULL) + return; + + /* print the usage message header */ + (void)fprintf(argv_error_stream, "%s%s\n", USAGE_LABEL, argv_program); + + /* run through the argument structure */ + for (arg_p = args; arg_p->ar_short_arg != ARGV_LAST; arg_p++) { + + /* skip or specifiers */ + if (arg_p->ar_short_arg == ARGV_OR || arg_p->ar_short_arg == ARGV_XOR) + continue; + + /* indent to the short-option col_c */ + (void)fprintf(argv_error_stream, "%*.*s", SHORT_COLUMN, SHORT_COLUMN, ""); + + /* start column counter */ + col_c = SHORT_COLUMN; + + /* print the short-arg stuff if there */ + if (arg_p->ar_short_arg == NULLC) { + (void)fputc('[', argv_error_stream); + col_c++; + } + else { + if (arg_p->ar_short_arg == ARGV_MAND) + display_arg(argv_error_stream, arg_p, COMMENT_COLUMN, &col_c); + else + display_option(argv_error_stream, arg_p, &col_c); + + /* put the long-option message on the correct column */ + if (col_c < LONG_COLUMN) { + (void)fprintf(argv_error_stream, "%*.*s", + LONG_COLUMN - col_c, LONG_COLUMN - col_c, ""); + col_c = LONG_COLUMN; + } + } + + /* print the long-option message */ + if (arg_p->ar_long_arg != NULL) { + len = COMMENT_COLUMN - col_c - (LONG_PREFIX_LENGTH + 1); + if (arg_p->ar_short_arg != NULLC) { + (void)fprintf(argv_error_stream, "%s", LONG_LABEL); + col_c += LONG_LABEL_LENGTH; + len -= LONG_LABEL_LENGTH; + } + (void)fprintf(argv_error_stream, "%s%-.*s", + LONG_PREFIX, len, arg_p->ar_long_arg); + col_c += LONG_PREFIX_LENGTH + MIN(len, strlen(arg_p->ar_long_arg)); + } + + /* add the optional argument if no short-arg */ + if (arg_p->ar_short_arg == NULLC) { + if (HAS_ARG(arg_p->ar_type)) { + (void)fputc(' ', argv_error_stream); + col_c++; + } + + /* display any optional arguments */ + display_arg(argv_error_stream, arg_p, COMMENT_COLUMN - 1, &col_c); + (void)fputc(']', argv_error_stream); + col_c++; + } + + /* print the comment */ + if (arg_p->ar_comment != NULL) { + /* put the comment message on the correct column */ + if (col_c < COMMENT_COLUMN) { + (void)fprintf(argv_error_stream, "%*.*s", + COMMENT_COLUMN - col_c, + COMMENT_COLUMN - col_c, ""); + col_c = COMMENT_COLUMN; + } + + len = SCREEN_WIDTH - col_c - COMMENT_LABEL_LENGTH; + (void)fprintf(argv_error_stream, "%s%-.*s", + COMMENT_LABEL, len, arg_p->ar_comment); + } + + (void)fprintf(argv_error_stream, "\n"); + } +} + +/* + * Do the usage depending on FLAG. + */ +LOCAL void do_usage(const argv_t *args, const int flag) +{ + if (argv_error_stream == NULL) + return; + + if (flag == GLOBAL_USAGE_SEE) + (void)fprintf(argv_error_stream, + "%*.*sUse the '%s%s' argument for assistance.\n", + (int)USAGE_LABEL_LENGTH, (int)USAGE_LABEL_LENGTH, "", + LONG_PREFIX, USAGE_ARG); + else if (flag == GLOBAL_USAGE_SHORT || flag == GLOBAL_USAGE_SHORTREM) + usage_short(args, flag); + else if (flag == GLOBAL_USAGE_LONG || flag == GLOBAL_USAGE_ALL) + usage_long(args); + + if (flag == GLOBAL_USAGE_ALL) { + (void)fprintf(argv_error_stream, "\n"); + (void)fprintf(argv_error_stream, + "%*.*sUse '%s%s' for default usage information.\n", + SHORT_COLUMN, SHORT_COLUMN, "", + LONG_PREFIX, USAGE_ARG); + (void)fprintf(argv_error_stream, + "%*.*sUse '%s%s' for short usage information.\n", + SHORT_COLUMN, SHORT_COLUMN, "", + LONG_PREFIX, USAGE_SHORT_ARG); + (void)fprintf(argv_error_stream, + "%*.*sUse '%s%s' for long usage information.\n", + SHORT_COLUMN, SHORT_COLUMN, "", + LONG_PREFIX, USAGE_LONG_ARG); + (void)fprintf(argv_error_stream, + "%*.*sUse '%s%s' for all usage information.\n", + SHORT_COLUMN, SHORT_COLUMN, "", + LONG_PREFIX, USAGE_ALL_ARG); + (void)fprintf(argv_error_stream, + "%*.*sUse '%s%s' to display the help message.\n", + SHORT_COLUMN, SHORT_COLUMN, "", + LONG_PREFIX, HELP_ARG); + (void)fprintf(argv_error_stream, + "%*.*sUse '%s%s' to display the version message.\n", + SHORT_COLUMN, SHORT_COLUMN, "", + LONG_PREFIX, VERSION_ARG); + (void)fprintf(argv_error_stream, + "%*.*sUse '%s%s' to display the options and their values.\n", + SHORT_COLUMN, SHORT_COLUMN, "", + LONG_PREFIX, DISPLAY_ARG); + } +} + +/******************************* preprocessing *******************************/ + +/* + * Preprocess argument array ARGS of NUM_ARGS entries and set the MAND + * and MAYBE boolean arrays. Returns [NO]ERROR. + */ +LOCAL int preprocess_array(argv_t *args, const int num_args) +{ + argv_t *arg_p; + char mand_array = ARGV_FALSE, maybe_field = ARGV_FALSE; + + /* count the args and find the first mandatory */ + for (arg_p = args; arg_p < args + num_args; arg_p++) { + + /* clear internal flags */ + arg_p->ar_type &= ~ARGV_FLAG_USED; + + /* do we have a mandatory-array? */ + if (arg_p->ar_short_arg == ARGV_MAND) { + if (mand_array) { + if (argv_error_stream != NULL) + (void)fprintf(argv_error_stream, + "%s: %s, no ARGV_MAND's can follow a MAND or MAYBE array\n", + argv_program, INTERNAL_ERROR_NAME); + if (argv_interactive) + (void)exit(EXIT_CODE); + return ERROR; + } + if (maybe_field) { + if (argv_error_stream != NULL) + (void)fprintf(argv_error_stream, + "%s: %s, no ARGV_MAND's can follow a ARGV_MAYBE\n", + argv_program, INTERNAL_ERROR_NAME); + if (argv_interactive) + (void)exit(EXIT_CODE); + return ERROR; + } + +#if 0 + if (arg_p->ar_long_arg != NULL) { + if (argv_error_stream != NULL) + (void)fprintf(argv_error_stream, + "%s: %s, ARGV_MAND's should not have long-options\n", + argv_program, INTERNAL_ERROR_NAME); + if (argv_interactive) + (void)exit(EXIT_CODE); + return ERROR; + } +#endif + + if (arg_p->ar_type & ARGV_ARRAY) + mand_array = ARGV_TRUE; + } + + /* do we have a maybe field? */ + if (arg_p->ar_short_arg == ARGV_MAYBE) { + if (mand_array) { + if (argv_error_stream != NULL) + (void)fprintf(argv_error_stream, + "%s: %s, no ARGV_MAYBE's can follow a MAND or MAYBE array\n", + argv_program, INTERNAL_ERROR_NAME); + if (argv_interactive) + (void)exit(EXIT_CODE); + return ERROR; + } + + maybe_field = ARGV_TRUE; + if (arg_p->ar_type & ARGV_ARRAY) + mand_array = ARGV_TRUE; + } + + /* handle initializing the argument array */ + if (arg_p->ar_type & ARGV_ARRAY) { + argv_array_t *arrp = (argv_array_t *)arg_p->ar_variable; + + if (! HAS_ARG(arg_p->ar_type)) { + if (argv_error_stream != NULL) + (void)fprintf(argv_error_stream, + "%s: %s, cannot have an array of boolean values\n", + argv_program, INTERNAL_ERROR_NAME); + if (argv_interactive) + (void)exit(EXIT_CODE); + return ERROR; + } + if (ARGV_TYPE(arg_p->ar_type) == ARGV_INCR) { + if (argv_error_stream != NULL) + (void)fprintf(argv_error_stream, + "%s: %s, cannot have an array of incremental values\n", + argv_program, INTERNAL_ERROR_NAME); + if (argv_interactive) + (void)exit(EXIT_CODE); + return ERROR; + } + arrp->aa_entryn = 0; + } + +#if 0 + /* must have a valid ar_short_arg */ + if (arg_p->ar_short_arg == NULLC) { + if (argv_error_stream != NULL) + (void)fprintf(argv_error_stream, + "%s: %s, short-option character is '\\0'\n", + argv_program, INTERNAL_ERROR_NAME); + if (argv_interactive) + (void)exit(EXIT_CODE); + return ERROR; + } +#endif + + /* verify variable pointer */ + if (arg_p->ar_variable == NULL + && arg_p->ar_short_arg != ARGV_OR + && arg_p->ar_short_arg != ARGV_XOR) { + if (argv_error_stream != NULL) + (void)fprintf(argv_error_stream, + "%s: %s, NULL variable specified in arg array\n", + argv_program, INTERNAL_ERROR_NAME); + if (argv_interactive) + (void)exit(EXIT_CODE); + return ERROR; + } + + /* verify [X]OR's */ + if (arg_p->ar_short_arg == ARGV_OR + || arg_p->ar_short_arg == ARGV_XOR) { + + /* that they are not at the start or end of list */ + if (arg_p == args || arg_p >= (args + num_args - 1)) { + if (argv_error_stream != NULL) + (void)fprintf(argv_error_stream, + "%s: %s, ARGV_[X]OR entries cannot be at start or end of array\n", + argv_program, INTERNAL_ERROR_NAME); + if (argv_interactive) + (void)exit(EXIT_CODE); + return ERROR; + } + + /* that two aren't next to each other */ + if ((arg_p - 1)->ar_short_arg == ARGV_OR + || (arg_p - 1)->ar_short_arg == ARGV_XOR) { + if (argv_error_stream != NULL) + (void)fprintf(argv_error_stream, + "%s: %s, two ARGV_[X]OR entries cannot be next to each other\n", + argv_program, INTERNAL_ERROR_NAME); + if (argv_interactive) + (void)exit(EXIT_CODE); + return ERROR; + } + } + } + + return NOERROR; +} + +/* + * Translate string argument ARG into VAR depending on its TYPE. + * Returns [NO]ERROR. + */ +LOCAL int translate_value(const char *arg, ARGV_PNT var, + const short type) +{ + argv_array_t *arr_p; + argv_type_t *type_p; + int val_type = ARGV_TYPE(type), size = 0; + + /* find the type and the size for array */ + for (type_p = argv_types; type_p->at_value != 0; type_p++) + if (type_p->at_value == val_type) { + size = type_p->at_size; + break; + } + + if (type_p->at_value == 0) { + if (argv_error_stream != NULL) + (void)fprintf(argv_error_stream, "%s: illegal variable type %d\n", + __FILE__, val_type); + return ERROR; + } + + if (type & ARGV_ARRAY) { + arr_p = (argv_array_t *)var; + + if (arr_p->aa_entryn == 0) + arr_p->aa_entries = (char *)malloc(ARRAY_INCR *size); + else if (arr_p->aa_entryn % ARRAY_INCR == 0) + arr_p->aa_entries = + (char *)realloc(arr_p->aa_entries, (arr_p->aa_entryn + ARRAY_INCR) * + size); + + if (arr_p->aa_entries == NULL) { + if (argv_error_stream != NULL) + (void)fprintf(argv_error_stream, + "%s: memory error during argument processing\n", + argv_program); + if (argv_interactive) + (void)exit(EXIT_CODE); + return ERROR; + } + + var = (char *)(arr_p->aa_entries) + arr_p->aa_entryn * size; + arr_p->aa_entryn++; + } + + /* translate depending on type */ + switch (val_type) { + + case ARGV_BOOL: + /* if no close argument, set to true */ + if (arg == NULL) + *(char *)var = ARGV_TRUE; + else if (*(char *)arg == 't' || *(char *)arg == 'T' + || *(char *)arg == 'y' || *(char *)arg == 'Y' + || *(char *)arg == '1') + *(char *)var = ARGV_TRUE; + else + *(char *)var = ARGV_FALSE; + break; + + case ARGV_CHAR: + *(char *)var = *(char *)arg; + break; + + case ARGV_CHARP: + *(char **)var = string_copy((char *)arg); + if (*(char **)var == NULL) + return ERROR; + break; + + case ARGV_FLOAT: + *(float *)var = (float)atof(arg); + break; + + case ARGV_SHORT: + *(short *)var = (short)atoi(arg); + break; + + case ARGV_INT: + *(int *)var = atoi(arg); + break; + + case ARGV_U_INT: + *(unsigned int *)var = atoi(arg); + break; + + case ARGV_LONG: + *(long *)var = atol(arg); + break; + + case ARGV_U_LONG: + *(unsigned long *)var = atol(arg); + break; + + case ARGV_BIN: + *(int *)var = btoi(arg); + break; + + case ARGV_OCT: + *(int *)var = otoi(arg); + break; + + case ARGV_HEX: + *(int *)var = htoi(arg); + break; + + case ARGV_BOOL_NEG: + /* if no close argument, set to false */ + if (arg == NULL) + *(char *)var = ARGV_FALSE; + else if (*(char *)arg == 't' || *(char *)arg == 'T' + || *(char *)arg == 'y' || *(char *)arg == 'Y' + || *(char *)arg == '1') + *(char *)var = ARGV_TRUE; + else + *(char *)var = ARGV_FALSE; + break; + + case ARGV_INCR: + /* if no close argument then increment else set the value */ + if (arg == NULL) + (*(int *)var)++; + else + *(int *)var = atoi(arg); + break; + + case ARGV_BOOL_ARG: + if (*(char *)arg == 't' || *(char *)arg == 'T' + || *(char *)arg == 'y' || *(char *)arg == 'Y' + || *(char *)arg == '1') + *(char *)var = ARGV_TRUE; + else + *(char *)var = ARGV_FALSE; + break; + } + + return NOERROR; +} + +/* + * Translate value from VAR into string STR depending on its TYPE. + */ +LOCAL void display_value(const ARGV_PNT var, const short type) +{ + int val_type = ARGV_TYPE(type); + + /* translate depending on type */ + switch (val_type) { + + case ARGV_BOOL: + case ARGV_BOOL_NEG: + case ARGV_BOOL_ARG: + if (*(char *)var) + (void)fprintf(argv_error_stream, "ARGV_TRUE"); + else + (void)fprintf(argv_error_stream, "ARGV_FALSE"); + break; + + case ARGV_CHAR: + (void)fprintf(argv_error_stream, "'%s'", expand_buf((char *)var, 1)); + break; + + case ARGV_CHARP: + { + int len; + if (*(char **)var == NULL) + (void)fprintf(argv_error_stream, "(null)"); + else { + len = strlen(*(char **)var); + (void)fprintf(argv_error_stream, "\"%s\"", + expand_buf(*(char **)var, len)); + } + } + break; + + case ARGV_FLOAT: + (void)fprintf(argv_error_stream, "%f", *(float *)var); + break; + + case ARGV_SHORT: + (void)fprintf(argv_error_stream, "%d", *(short *)var); + break; + + case ARGV_INT: + case ARGV_INCR: + (void)fprintf(argv_error_stream, "%d", *(int *)var); + break; + + case ARGV_U_INT: + (void)fprintf(argv_error_stream, "%u", *(unsigned int *)var); + break; + + case ARGV_LONG: + (void)fprintf(argv_error_stream, "%ld", *(long *)var); + break; + + case ARGV_U_LONG: + (void)fprintf(argv_error_stream, "%lu", *(unsigned long *)var); + break; + + /* this should be a routine */ + case ARGV_BIN: + { + int bit_c; + char first = ARGV_FALSE; + + (void)fputc('0', argv_error_stream); + + if (*(int *)var != 0) { + (void)fputc('b', argv_error_stream); + + for (bit_c = sizeof(int) * BITS_IN_BYTE - 1; bit_c >= 0; bit_c--) { + int bit = *(int *)var & (1 << bit_c); + + if (bit == 0) { + if (first) + (void)fputc('0', argv_error_stream); + } + else { + (void)fputc('1', argv_error_stream); + first = ARGV_TRUE; + } + } + } + } + break; + + case ARGV_OCT: + (void)fprintf(argv_error_stream, "%#o", *(int *)var); + break; + + case ARGV_HEX: + (void)fprintf(argv_error_stream, "%#x", *(int *)var); + break; + } +} + +/* + * Translate value from VAR into string STR depending on its TYPE. + */ +LOCAL void display_variables(const argv_t *args) +{ + const argv_t *arg_p; + argv_type_t *type_p; + + /* run through the argument structure */ + for (arg_p = args; arg_p->ar_short_arg != ARGV_LAST; arg_p++) { + int col_c, val_type = ARGV_TYPE(arg_p->ar_type); + + /* skip or specifiers */ + if (arg_p->ar_short_arg == ARGV_OR || arg_p->ar_short_arg == ARGV_XOR) + continue; + + col_c = 0; + if (arg_p->ar_short_arg == ARGV_MAND) + display_arg(argv_error_stream, arg_p, COMMENT_COLUMN, &col_c); + else + display_option(argv_error_stream, arg_p, &col_c); + + /* put the type in the correct column */ + if (col_c < LONG_COLUMN) { + (void)fprintf(argv_error_stream, "%*.*s", + LONG_COLUMN - col_c, LONG_COLUMN - col_c, ""); + col_c = LONG_COLUMN; + } + + /* find the type */ + type_p = NULL; + for (type_p = argv_types; type_p->at_value != 0; type_p++) { + if (type_p->at_value == ARGV_TYPE(arg_p->ar_type)) { + int len, tlen; + + len = COMMENT_COLUMN - col_c - 1; + tlen = strlen(type_p->at_name); + (void)fprintf(argv_error_stream, " %-.*s", len, type_p->at_name); + col_c += MIN(len, tlen); + if (arg_p->ar_type & ARGV_ARRAY) { + (void)fprintf(argv_error_stream, "%s", ARRAY_LABEL); + col_c += sizeof(ARRAY_LABEL) - 1; + } + break; + } + } + + if (col_c < COMMENT_COLUMN) { + (void)fprintf(argv_error_stream, "%*.*s", + COMMENT_COLUMN - col_c, COMMENT_COLUMN - col_c, ""); + col_c = COMMENT_COLUMN; + } + + if (arg_p->ar_type & ARGV_ARRAY) { + argv_array_t *arr_p; + int entry_c, size = 0; + + /* find the type and the size for array */ + if (type_p == NULL) { + (void)fprintf(argv_error_stream, "%s: illegal variable type %d\n", + __FILE__, val_type); + continue; + } + size = type_p->at_size; + arr_p = (argv_array_t *)arg_p->ar_variable; + + if (arr_p->aa_entryn == 0) + (void)fprintf(argv_error_stream, "no entries"); + else { + for (entry_c = 0; entry_c < arr_p->aa_entryn; entry_c++) { + ARGV_PNT var; + if (entry_c > 0) + (void)fputc(',', argv_error_stream); + var = (char *)(arr_p->aa_entries) + entry_c * size; + display_value(var, val_type); + } + } + } + else + display_value(arg_p->ar_variable, val_type); + (void)fputc('\n', argv_error_stream); + } +} + +/************************** checking used arguments **************************/ + +/* + * Check out if WHICH argument from ARGS has an *or* specified + * attached to it. Returns [NO]ERROR + */ +LOCAL int check_or(const argv_t *args, const argv_t *which) +{ + const argv_t *arg_p, *match_p = NULL; + + /* check ORs below */ + for (arg_p = which - 2; arg_p >= args; arg_p -= 2) { + if ((arg_p + 1)->ar_short_arg != ARGV_OR + && (arg_p + 1)->ar_short_arg != ARGV_XOR) + break; + if (arg_p->ar_type & ARGV_FLAG_USED) { + match_p = arg_p; + break; + } + } + + /* check ORs above */ + if (match_p == NULL) { + /* NOTE: we assume that which is not pointing now to ARGV_LAST */ + for (arg_p = which + 2; + arg_p->ar_short_arg != ARGV_LAST + && (arg_p - 1)->ar_short_arg != ARGV_LAST; + arg_p += 2) { + if ((arg_p - 1)->ar_short_arg != ARGV_OR + && (arg_p - 1)->ar_short_arg != ARGV_XOR) + break; + if (arg_p->ar_type & ARGV_FLAG_USED) { + match_p = arg_p; + break; + } + } + } + + /* did we not find a problem? */ + if (match_p == NULL) + return NOERROR; + + if (argv_error_stream == NULL) + return ERROR; + + (void)fprintf(argv_error_stream, + "%s: %s, specify only one of the following:\n", + argv_program, USAGE_ERROR_NAME); + + /* little hack to print the one that matched and the one we were checking */ + for (;;) { + if (match_p->ar_long_arg == NULL) + (void)fprintf(argv_error_stream, " %s%c\n", + SHORT_PREFIX, match_p->ar_short_arg); + else + (void)fprintf(argv_error_stream, " %s%c (%s%s)\n", + SHORT_PREFIX, match_p->ar_short_arg, + LONG_PREFIX, match_p->ar_long_arg); + + if (match_p == which) + break; + match_p = which; + } + + return ERROR; +} + +/* + * Find all the XOR arguments and make sure each group has at least + * one specified in it. Returns [NO]ERROR. + */ +LOCAL int check_xor(const argv_t *args) +{ + const argv_t *start_p = NULL, *arg_p; + + /* run through the list of arguments */ + for (arg_p = args; arg_p->ar_short_arg != ARGV_LAST; arg_p++) { + + /* only check the XORs */ + if (arg_p->ar_short_arg != ARGV_XOR) + continue; + + start_p = arg_p; + + /* + * NOTE: we are guaranteed that we are on a XOR so there is + * something below and above... + */ + if ((arg_p - 1)->ar_type & ARGV_FLAG_USED) + start_p = NULL; + + /* run through all XORs */ + for (;;) { + arg_p++; + if (arg_p->ar_type & ARGV_FLAG_USED) + start_p = NULL; + if ((arg_p + 1)->ar_short_arg != ARGV_XOR) + break; + arg_p++; + } + + /* were none of the xor's filled? */ + if (start_p != NULL) + break; + } + + /* did we not find a problem? */ + if (start_p == NULL) + return NOERROR; + + /* arg_p points to the first XOR which failed */ + if (argv_error_stream == NULL) + return ERROR; + + (void)fprintf(argv_error_stream, "%s: %s, must specify one of:\n", + argv_program, USAGE_ERROR_NAME); + + for (arg_p = start_p;; arg_p += 2) { + /* + * NOTE: we are guaranteed that we are on a XOR so there is + * something below and above... + */ + (void)fprintf(argv_error_stream, " %s%c", + SHORT_PREFIX, (arg_p - 1)->ar_short_arg); + if ((arg_p - 1)->ar_long_arg != NULL) + (void)fprintf(argv_error_stream, " (%s%s)", + LONG_PREFIX, (arg_p - 1)->ar_long_arg); + (void)fprintf(argv_error_stream, "\n"); + + if (arg_p->ar_short_arg != ARGV_XOR) + break; + } + + return ERROR; +} + +/* + * Check to see if any mandatory arguments left. Returns [NO]ERROR. + */ +LOCAL int check_mand(const argv_t *args) +{ + const argv_t *arg_p; + int slot_c = 0; + + /* see if there are any mandatory args left */ + for (arg_p = args; arg_p->ar_short_arg != ARGV_LAST; arg_p++) + if (arg_p->ar_short_arg == ARGV_MAND + && (! (arg_p->ar_type & ARGV_FLAG_USED)) + && ! (arg_p->ar_type & ARGV_ARRAY)) + slot_c++; + + if (slot_c > 0) { + if (argv_error_stream != NULL) + (void)fprintf(argv_error_stream, + "%s: %s, %d more mandatory argument%s must be specified\n", + argv_program, USAGE_ERROR_NAME, + slot_c, (slot_c == 1 ? "" : "s")); + return ERROR; + } + + return NOERROR; +} + +/* + * Check for any missing argument options. Returns [NO]ERROR + */ +LOCAL int check_opt(void) +{ + int queue_c; + + queue_c = QUEUE_COUNT(); + if (queue_c > 0) { + if (argv_error_stream != NULL) + (void)fprintf(argv_error_stream, + "%s: %s, %d more option-argument%s must be specified\n", + argv_program, USAGE_ERROR_NAME, + queue_c, (queue_c == 1 ? "" : "s")); + return ERROR; + } + + return NOERROR; +} + +/**************************** argument processing ****************************/ + +/* + * Read in arguments from PATH and run them from the GRID. OKAY_P is + * a pointer to the boolean error marker. Returns [NO]ERROR. + */ +LOCAL void file_args(const char *path, argv_t *grid, char *okay_p) +{ + char **argv, **argv_p; + int argc, max; + FILE *infile; + char line[FILE_LINE_SIZE + 1], *line_p; + + /* open the input file */ + infile = fopen(path, "r"); + if (infile == NULL) { + *okay_p = ARGV_FALSE; + if (argv_error_stream != NULL) + (void)fprintf(argv_error_stream, + "%s: could not load command-line arguments from: %s\n", + argv_program, path); + if (argv_interactive) + (void)exit(EXIT_CODE); + return; + } + + /* get an array of char * */ + argc = 0; + max = ARRAY_INCR; + argv = malloc(sizeof(char *) * max); + if (argv == NULL) { + *okay_p = ARGV_FALSE; + (void)fclose(infile); + if (argv_error_stream != NULL) + (void)fprintf(argv_error_stream, + "%s: memory error during argument processing\n", + argv_program); + if (argv_interactive) + (void)exit(EXIT_CODE); + return; + } + argv_p = argv; + + /* read in the file lines */ + while (fgets(line, FILE_LINE_SIZE, infile) != NULL) { + /* punch the \n at end of line */ + for (line_p = line; *line_p != '\n' && *line_p != '\0'; line_p++); + *line_p = '\0'; + + *argv_p = string_copy(line); + if (*argv_p == NULL) { + *okay_p = ARGV_FALSE; + return; + } + + argv_p++; + argc++; + if (argc == max) { + max += ARRAY_INCR; + argv = realloc(argv, sizeof(char *) * max); + if (argv == NULL) { + *okay_p = ARGV_FALSE; + (void)fclose(infile); + if (argv_error_stream != NULL) + (void)fprintf(argv_error_stream, + "%s: memory error during argument processing\n", + argv_program); + if (argv_interactive) + (void)exit(EXIT_CODE); + return; + } + argv_p = argv + argc; + } + } + + /* now do the list */ + do_list(grid, argc, argv, okay_p); + + /* now free up the list */ + for (argv_p = argv; argv_p < argv + argc; argv_p++) + free(*argv_p); + free(argv); + + (void)fclose(infile); +} + +/* + * Process an argument in MATCH_P which looking at GRID. sets okay_p to + * FALSE if the argument was not okay. + */ +LOCAL void do_arg(argv_t *grid, argv_t *match_p, const char *close_p, + char *okay_p) +{ + if (global_multi == GLOBAL_MULTI_REJECT) { + /* + * have we used this one before? + * NOTE: should this be a warning or a non-error altogether? + */ + if (match_p->ar_type & ARGV_FLAG_USED + && (! (match_p->ar_type & ARGV_ARRAY)) + && ARGV_TYPE(match_p->ar_type) != ARGV_INCR) { + if (argv_error_stream != NULL) + (void)fprintf(argv_error_stream, + "%s: %s, you've already specified the '%c' argument\n", + argv_program, USAGE_ERROR_NAME, + match_p->ar_short_arg); + *okay_p = ARGV_FALSE; + } + } + + /* we used this argument */ + match_p->ar_type |= ARGV_FLAG_USED; + + /* check arguments that must be OR'd */ + if (check_or(grid, match_p) != NOERROR) { + /* + * don't return here else we might generate an XOR error + * because the argument wasn't specified + */ + *okay_p = ARGV_FALSE; + } + + /* + * If we have a close argument, pass to translate. If it is a + * boolean or increment variable, then pass in a value of null + * else queue it for needing a value argument. + */ + if (global_close == GLOBAL_CLOSE_ENABLE && close_p != NULL) { + if (translate_value(close_p, match_p->ar_variable, + match_p->ar_type) != NOERROR) + *okay_p = ARGV_FALSE; + } + else if (! HAS_ARG(match_p->ar_type)) { + if (translate_value(NULL, match_p->ar_variable, + match_p->ar_type) != NOERROR) + *okay_p = ARGV_FALSE; + } + else if (global_close == GLOBAL_CLOSE_ENABLE && close_p != NULL) { + if (translate_value(close_p, match_p->ar_variable, + match_p->ar_type) != NOERROR) + *okay_p = ARGV_FALSE; + } + else + QUEUE_ENQUEUE(match_p); +} + +/* + * Process a list of arguments ARGV and ARGV as it applies to ARGS. + * on NUM_ARGS members. OKAY_P is a pointer to the boolean error + * marker. + */ +LOCAL void do_list(argv_t *grid, const int argc, char **argv, + char *okay_p) +{ + argv_t *grid_p, *match_p; + int len, char_c, unwant_c = 0; + char last_arg = ARGV_FALSE; + char *close_p = NULL, **arg_p; + + /* run throught rest of arguments */ + for (arg_p = argv; arg_p < argv + argc; arg_p++) { + + /* have we reached the LAST_ARG marker? */ + if (! last_arg && strcmp(LAST_ARG, *arg_p) == 0) { + last_arg = ARGV_TRUE; + continue; + } + + /* check for close equals marker */ + if (! last_arg && global_close == GLOBAL_CLOSE_ENABLE) { + close_p = strchr(*arg_p, ARG_EQUALS); + /* if we find the special char then punch the null and set pointer */ + if (close_p != NULL) { + *close_p = NULLC; + close_p++; + } + } + + /* are we processing a long option? */ + if (! last_arg && strncmp(LONG_PREFIX, *arg_p, LONG_PREFIX_LENGTH) == 0) { + + /* get length of rest of argument */ + len = strlen(*arg_p) - LONG_PREFIX_LENGTH; + + /* we need more than the prefix */ + if (len <= 0) { + if (argv_error_stream != NULL) + (void)fprintf(argv_error_stream, + "%s: %s, empty long-option prefix '%s'\n", + argv_program, USAGE_ERROR_NAME, *arg_p); + *okay_p = ARGV_FALSE; + continue; + } + + match_p = NULL; + + /* run though long options looking for a match */ + for (grid_p = grid; grid_p->ar_short_arg != ARGV_LAST; grid_p++) { + if (grid_p->ar_long_arg == NULL) + continue; + + if (strncmp(*arg_p + LONG_PREFIX_LENGTH, + grid_p->ar_long_arg, len) == 0) { + if (match_p != NULL) { + if (argv_error_stream != NULL) + (void)fprintf(argv_error_stream, + "%s: %s, '%s' might be '%s' or '%s'\n", + argv_program, USAGE_ERROR_NAME, *arg_p, + grid_p->ar_long_arg, match_p->ar_long_arg); + *okay_p = ARGV_FALSE; + break; + } + + /* record a possible match */ + match_p = grid_p; + + /* don't break, need to see if another one matches */ + } + } + + /* if we found a match but quit then we must have found two matches */ + if (match_p != NULL && grid_p->ar_short_arg != ARGV_LAST) + continue; + + if (match_p != NULL) { + (void)do_arg(grid, match_p, close_p, okay_p); + continue; + } + + /* we did not find long-option match */ + + /* check for special file value */ + if (strncmp(FILE_ARG, *arg_p + LONG_PREFIX_LENGTH, len) == 0) { + if (global_close == GLOBAL_CLOSE_ENABLE && close_p != NULL) { + /* open the file and read in the args */ + file_args(close_p, grid, okay_p); + } + else { + /* HACK: we enqueue null for the file argument */ + QUEUE_ENQUEUE(NULL); + } + continue; + } + + /* check for special usage value */ + if (strncmp(USAGE_ARG, *arg_p + LONG_PREFIX_LENGTH, len) == 0) { + if (argv_interactive) { + do_usage(grid, global_usage); + (void)exit(0); + } + continue; + } + + /* check for special short-usage value */ + if (strncmp(USAGE_SHORT_ARG, *arg_p + LONG_PREFIX_LENGTH, len) == 0) { + if (argv_interactive) { + do_usage(grid, GLOBAL_USAGE_SHORT); + (void)exit(0); + } + continue; + } + + /* check for special long-usage value */ + if (strncmp(USAGE_LONG_ARG, *arg_p + LONG_PREFIX_LENGTH, len) == 0) { + if (argv_interactive) { + do_usage(grid, GLOBAL_USAGE_LONG); + (void)exit(0); + } + continue; + } + + /* check for special long-usage value */ + if (strncmp(USAGE_ALL_ARG, *arg_p + LONG_PREFIX_LENGTH, len) == 0) { + if (argv_interactive) { + do_usage(grid, GLOBAL_USAGE_ALL); + (void)exit(0); + } + continue; + } + + /* check for special help value */ + if (strncmp(HELP_ARG, *arg_p + LONG_PREFIX_LENGTH, len) == 0) { + if (argv_interactive) { + if (argv_error_stream != NULL) { + if (argv_help_string == NULL) + (void)fprintf(argv_error_stream, + "%s: I'm sorry, no help is available.\n", + argv_program); + else + (void)fprintf(argv_error_stream, "%s\n", argv_help_string); + } + (void)exit(0); + } + continue; + } + + /* check for special version value */ + if (strncmp(VERSION_ARG, *arg_p + LONG_PREFIX_LENGTH, len) == 0) { + if (argv_interactive) { + if (argv_error_stream != NULL) { + if (argv_version_string == NULL) + (void)fprintf(argv_error_stream, + "%s: no version information is available.\n", + argv_program); + else + (void)fprintf(argv_error_stream, "%s\n", argv_version_string); + } + (void)exit(0); + } + continue; + } + + /* check for display arguments value */ + if (strncmp(DISPLAY_ARG, *arg_p + LONG_PREFIX_LENGTH, len) == 0) { + if (argv_interactive) { + if (argv_error_stream != NULL) + display_variables(grid); + (void)exit(0); + } + continue; + } + + if (argv_error_stream != NULL) + (void)fprintf(argv_error_stream, + "%s: %s, unknown long option '%s'.\n", + argv_program, USAGE_ERROR_NAME, *arg_p); + *okay_p = ARGV_FALSE; + continue; + } + + /* are we processing a short option? */ + if (! last_arg + && strncmp(SHORT_PREFIX, *arg_p, SHORT_PREFIX_LENGTH) == 0) { + + /* get length of rest of argument */ + len = strlen(*arg_p) - SHORT_PREFIX_LENGTH; + + /* we need more than the prefix */ + if (len <= 0) { + if (argv_error_stream != NULL) + (void)fprintf(argv_error_stream, + "%s: %s, empty short-option prefix '%s'\n", + argv_program, USAGE_ERROR_NAME, *arg_p); + *okay_p = ARGV_FALSE; + continue; + } + + /* run through the chars in this option */ + for (char_c = 0; char_c < len; char_c++) { + + /* run through the arg list looking for a match */ + for (match_p = grid; match_p->ar_short_arg != ARGV_LAST; match_p++) + if (match_p->ar_short_arg == (*arg_p)[SHORT_PREFIX_LENGTH + char_c]) + break; + + /* did we not find argument? */ + if (match_p->ar_short_arg == ARGV_LAST) { + + /* check for special usage value */ + if ((*arg_p)[SHORT_PREFIX_LENGTH + char_c] == USAGE_CHAR_ARG) { + if (argv_interactive) { + do_usage(grid, global_usage); + (void)exit(0); + } + continue; + } + + /* create an error string */ + if (argv_error_stream != NULL) + (void)fprintf(argv_error_stream, + "%s: %s, unknown short option '%s%c'.\n", + argv_program, USAGE_ERROR_NAME, SHORT_PREFIX, + (*arg_p)[SHORT_PREFIX_LENGTH + char_c]); + *okay_p = ARGV_FALSE; + continue; + } + + do_arg(grid, match_p, close_p, okay_p); + } + + continue; + } + + /* could this be a value? */ + if (grid->ar_short_arg != ARGV_LAST && QUEUE_COUNT() > 0) { + QUEUE_DEQUEUE(match_p); + /* HACK: is this the file argument */ + if (match_p == NULL) + file_args(close_p, grid, okay_p); + else + if (translate_value(*arg_p, match_p->ar_variable, + match_p->ar_type) != NOERROR) + *okay_p = ARGV_FALSE; + continue; + } + + /* process mandatory args if some left to process */ + for (grid_p = grid; grid_p->ar_short_arg != ARGV_LAST; grid_p++) + if (grid_p->ar_short_arg == ARGV_MAND + && (! (grid_p->ar_type & ARGV_FLAG_USED))) + break; + if (grid_p->ar_short_arg != ARGV_LAST) { + /* absorb another mand. arg */ + if (translate_value(*arg_p, grid_p->ar_variable, + grid_p->ar_type) != NOERROR) + *okay_p = ARGV_FALSE; + if (! (grid_p->ar_type & ARGV_ARRAY)) + grid_p->ar_type |= ARGV_FLAG_USED; + continue; + } + + /* process maybe args if some left to process */ + for (grid_p = grid; grid_p->ar_short_arg != ARGV_LAST; grid_p++) + if (grid_p->ar_short_arg == ARGV_MAYBE + && (! (grid_p->ar_type & ARGV_FLAG_USED))) + break; + if (grid_p->ar_short_arg != ARGV_LAST) { + /* absorb another maybe arg */ + if (translate_value(*arg_p, grid_p->ar_variable, + grid_p->ar_type) != NOERROR) + *okay_p = ARGV_FALSE; + if (! (grid_p->ar_type & ARGV_ARRAY)) + grid_p->ar_type |= ARGV_FLAG_USED; + continue; + } + + /* default is an error */ + unwant_c++; + *okay_p = ARGV_FALSE; + } + + if (unwant_c > 0 && argv_error_stream != NULL) + (void)fprintf(argv_error_stream, + "%s: %s, %d unwanted additional argument%s\n", + argv_program, USAGE_ERROR_NAME, + unwant_c, (unwant_c == 1 ? "" : "s")); +} + +/****************************** env processing *******************************/ + +/* + * Handle the args from the ENV variable. Returns [NO]ERROR. + */ +LOCAL int do_env_args(argv_t *args, char *okay_p) +{ + int env_c, env_n; + char **vect_p, env_name[256], *environ_p; + + /* create the env variable */ + (void)sprintf(env_name, ENVIRON_FORMAT, argv_program); + + /* NOTE: by default the env name is all uppercase */ + for (environ_p = env_name; *environ_p != NULLC; environ_p++) + if (islower((int)*environ_p)) + *environ_p = toupper((int)*environ_p); + + environ_p = getenv(env_name); + if (environ_p == NULL) + return NOERROR; + + /* break the list into tokens and do the list */ + environ_p = string_copy(environ_p); + if (environ_p == NULL) + return ERROR; + + vect_p = vectorize(environ_p, " \t", &env_n); + if (vect_p != NULL) { + do_list(args, env_n, vect_p, okay_p); + + /* free token list */ + for (env_c = 0; env_c < env_n; env_c++) + free(vect_p[env_c]); + free(vect_p); + } + free(environ_p); + + return NOERROR; +} + +/* + * Process the global env variable. Returns [NO]ERROR. + */ +LOCAL int process_env(void) +{ + static char done = ARGV_FALSE; + char *environ_p, *env_p, *arg; + int len; + + /* make sure we only do this once */ + if (done) + return NOERROR; + + done = ARGV_TRUE; + + /* get the argv information */ + environ_p = getenv(GLOBAL_NAME); + if (environ_p == NULL) + return NOERROR; + + /* save a copy of it */ + environ_p = string_copy(environ_p); + if (environ_p == NULL) + return ERROR; + + arg = environ_p; + + for (;;) { + env_p = strtok(arg, " \t,:"); + if (env_p == NULL) + break; + arg = NULL; + + len = strlen(GLOBAL_CLOSE); + if (strncmp(GLOBAL_CLOSE, env_p, len) == 0) { + env_p += len; + if (strcmp(env_p, "disable") == 0) + global_close = GLOBAL_CLOSE_DISABLE; + else if (strcmp(env_p, "enable") == 0) + global_close = GLOBAL_CLOSE_ENABLE; + else { + if (argv_error_stream != NULL) + (void)fprintf(argv_error_stream, + "%s: illegal env variable '%s' '%s' argument '%s'\n", + __FILE__, GLOBAL_NAME, GLOBAL_CLOSE, env_p); + } + continue; + } + + len = strlen(GLOBAL_ENV); + if (strncmp(GLOBAL_ENV, env_p, len) == 0) { + env_p += len; + if (strcmp(env_p, "none") == 0) + global_env = GLOBAL_ENV_NONE; + else if (strcmp(env_p, "before") == 0) + global_env = GLOBAL_ENV_BEFORE; + else if (strcmp(env_p, "after") == 0) + global_env = GLOBAL_ENV_AFTER; + else { + if (argv_error_stream != NULL) + (void)fprintf(argv_error_stream, + "%s: illegal env variable '%s' '%s' argument '%s'\n", + __FILE__, GLOBAL_NAME, GLOBAL_ENV, env_p); + } + continue; + } + + len = strlen(GLOBAL_ERROR); + if (strncmp(GLOBAL_ERROR, env_p, len) == 0) { + env_p += len; + if (strcmp(env_p, "none") == 0) + global_error = GLOBAL_ERROR_NONE; + else if (strcmp(env_p, "see") == 0) + global_error = GLOBAL_ERROR_SEE; + else if (strcmp(env_p, "short") == 0) + global_error = GLOBAL_ERROR_SHORT; + else if (strcmp(env_p, "shortrem") == 0) + global_error = GLOBAL_ERROR_SHORTREM; + else if (strcmp(env_p, "long") == 0) + global_error = GLOBAL_ERROR_LONG; + else if (strcmp(env_p, "all") == 0) + global_error = GLOBAL_ERROR_ALL; + else { + if (argv_error_stream != NULL) + (void)fprintf(argv_error_stream, + "%s: illegal env variable '%s' '%s' argument '%s'\n", + __FILE__, GLOBAL_NAME, GLOBAL_ERROR, env_p); + } + continue; + } + + len = strlen(GLOBAL_MULTI); + if (strncmp(GLOBAL_MULTI, env_p, len) == 0) { + env_p += len; + if (strcmp(env_p, "reject") == 0) + global_multi = GLOBAL_MULTI_REJECT; + else if (strcmp(env_p, "accept") == 0) + global_multi = GLOBAL_MULTI_ACCEPT; + else { + if (argv_error_stream != NULL) + (void)fprintf(argv_error_stream, + "%s: illegal env variable '%s' '%s' argument '%s'\n", + __FILE__, GLOBAL_NAME, GLOBAL_MULTI, env_p); + } + continue; + } + + len = strlen(GLOBAL_USAGE); + if (strncmp(GLOBAL_USAGE, env_p, len) == 0) { + env_p += len; + if (strcmp(env_p, "short") == 0) + global_usage = GLOBAL_USAGE_SHORT; + else if (strcmp(env_p, "shortrem") == 0) + global_usage = GLOBAL_USAGE_SHORTREM; + else if (strcmp(env_p, "long") == 0) + global_usage = GLOBAL_USAGE_LONG; + else if (strcmp(env_p, "all") == 0) + global_usage = GLOBAL_USAGE_ALL; + else { + if (argv_error_stream != NULL) + (void)fprintf(argv_error_stream, + "%s: illegal env variable '%s' '%s' argument '%s'\n", + __FILE__, GLOBAL_NAME, GLOBAL_USAGE, env_p); + } + continue; + } + + if (argv_error_stream != NULL) + (void)fprintf(argv_error_stream, + "%s: illegal env variable '%s' setting '%s'\n", + __FILE__, GLOBAL_NAME, env_p); + } + + free(environ_p); + return NOERROR; +} + +/* + * Processes ARGS from ARGC and ARGV. Returns 0 if no error else -1. + */ +LOCAL int process_args(argv_t *args, const int argc, char **argv) +{ + int num_args; + const char *prog_p; + char okay = ARGV_TRUE; + argv_t *arg_p; + + if (process_env() != NOERROR) + return ERROR; + + if (args == NULL) + args = empty; + + if (argc < 0) { + if (argv_error_stream != NULL) + (void)fprintf(argv_error_stream, + "%s: %s, argc argument to argv_process is %d\n", + __FILE__, INTERNAL_ERROR_NAME, argc); + if (argv_interactive) + (void)exit(EXIT_CODE); + return ERROR; + } + + if (argv == NULL) { + if (argv_error_stream != NULL) + (void)fprintf(argv_error_stream, + "%s: %s, argv argument to argv_process is NULL\n", + __FILE__, INTERNAL_ERROR_NAME); + if (argv_interactive) + (void)exit(EXIT_CODE); + return ERROR; + } + + /* set global variables */ + argv_argv = argv; + argv_argc = argc; + + /* build the program name from the argv[0] path */ + { + const char *tmp_p; + + prog_p = *argv; + for (tmp_p = *argv; *tmp_p != NULLC; tmp_p++) + if (*tmp_p == '/') + prog_p = tmp_p + 1; + } + + /* so we can step on the environmental space */ + (void)strncpy(argv_program, prog_p, PROGRAM_NAME); + + /* count the args */ + num_args = 0; + for (arg_p = args; arg_p->ar_short_arg != ARGV_LAST; arg_p++) + num_args++; + + /* verify the argument array */ + if (preprocess_array(args, num_args) != NOERROR) + return ERROR; + + /* allocate our value queue */ + if (num_args > 0) + QUEUE_ALLOC(argv_t *, num_args); + + /* do the env args before? */ + if (global_env == GLOBAL_ENV_BEFORE) { + if (do_env_args(args, &okay) != NOERROR) + return ERROR; + } + + /* do the external args */ + do_list(args, argc - 1, argv + 1, &okay); + + /* do the env args after? */ + if (global_env == GLOBAL_ENV_AFTER) { + do_env_args(args, &okay); + if (do_env_args(args, &okay) != NOERROR) + return ERROR; + } + + /* make sure the XOR and MAND args and argument-options are okay */ + if (check_mand(args) != NOERROR) + okay = ARGV_FALSE; + if (check_opt() != NOERROR) + okay = ARGV_FALSE; + if (check_xor(args) != NOERROR) + okay = ARGV_FALSE; + + /* if we allocated the space then free it */ + if (num_args > 0) + QUEUE_FREE(); + + /* was there an error? */ + if (! okay) { + if (argv_error_stream != NULL) + do_usage(args, global_error); + if (argv_interactive) + (void)exit(EXIT_CODE); + return ERROR; + } + + return NOERROR; +} + +/***************************** exported routines *****************************/ + +/* + * Processes ARGC number of arguments from ARGV depending on argument + * info array ARGS (if null then an empty array is used). This + * routine will not modify the argv array in any way. + * + * NOTE: it will modify the args array by setting various flags in the + * type field. returns 0 if no error else -1. + */ +EXPORT int argv_process(argv_t *args, const int argc, char **argv) +{ + if (! enabled) + argv_startup(); + + if (process_args(args, argc, argv) == NOERROR) + return NOERROR; + else + return ERROR; +} + +/* + * Processes arguments sent in via the STRING that a web-server might + * send to program in ARG0. Use DELIM to set up the delimiters of the + * arguments in the string. query_string processing should have "&" + * and path_info should have "/". You may want to add "=" if you use + * arg=value. The '=' delimiter is treated as special so //x=// will + * strip the extra /'s in a row but will create a null argument for x. + * + * WARNING: you cannot use argv_copy_args after this is called because a + * temporary grid is created. returns 0 on noerror else -1. + */ +EXPORT int argv_web_process_string(argv_t *args, const char *arg0, + const char *string, + const char *delim) +{ + const char *str_p, *delim_p, *delim_str; + char *copy, *copy_p, **argv; + int argc, ret, alloced; + + if (! enabled) + argv_startup(); + + if (delim == NULL) + delim_str = ""; + else + delim_str = delim; + + /* copy incoming string so we can punch nulls */ + copy = malloc(strlen(string) + 1); + if (copy == NULL) { + if (argv_error_stream != NULL) + (void)fprintf(argv_error_stream, + "%s: memory error during argument processing\n", + argv_program); + if (argv_interactive) + (void)exit(EXIT_CODE); + return ERROR; + } + + /* create argv array */ + alloced = ARG_MALLOC_INCR; + argv = (char **)malloc(sizeof(char *) * alloced); + if (argv == NULL) { + free(copy); + if (argv_error_stream != NULL) + (void)fprintf(argv_error_stream, + "%s: memory error during argument processing\n", + argv_program); + if (argv_interactive) + (void)exit(EXIT_CODE); + return ERROR; + } + + argc = 0; + argv[argc++] = (char *)arg0; + str_p = string; + /* skip starting multiple arg delimiters */ + for (; *str_p != NULLC; str_p++) { + for (delim_p = delim_str; *delim_p != NULLC; delim_p++) + if (*str_p == *delim_p) + break; + if (*delim_p == NULLC) + break; + } + + /* start of the string is argv[1] */ + if (*str_p != NULLC) { + if (argc >= alloced) { + alloced += ARG_MALLOC_INCR; + argv = (char **)realloc(argv, sizeof(char *) * alloced); + if (argv == NULL) { + free(copy); + if (argv_error_stream != NULL) + (void)fprintf(argv_error_stream, + "%s: memory error during argument processing\n", + argv_program); + if (argv_interactive) + (void)exit(EXIT_CODE); + return ERROR; + } + } + argv[argc++] = copy; + } + + for (copy_p = copy;; str_p++) { + int val; + + /* are we done? */ + if (*str_p == NULLC) { + *copy_p = NULLC; + break; + } + + /* is this a argument seperator? */ + for (delim_p = delim_str; *delim_p != NULLC; delim_p++) + if (*str_p == *delim_p) + break; + if (*delim_p != NULLC) { + *copy_p++ = NULLC; + + /* + * look ahead and skip multiple arg delimiters. we have a + * special case if the delimiter is '='. This means that we + * need to generate a null string argument. + */ + if (*str_p != '=') { + for (;; str_p++) { + for (delim_p = delim_str; *delim_p != NULLC; delim_p++) + if (*(str_p + 1) == *delim_p) + break; + if (*delim_p == NULLC) + break; + } + } + + /* if we are not at the end of the string, create a new arg */ + if (*str_p == '=' || *(str_p + 1) != NULLC) { + if (argc >= alloced) { + alloced += ARG_MALLOC_INCR; + argv = (char **)realloc(argv, sizeof(char *) * alloced); + if (argv == NULL) { + if (argv_error_stream != NULL) + (void)fprintf(argv_error_stream, + "%s: memory error during argument processing\n", + argv_program); + if (argv_interactive) + (void)exit(EXIT_CODE); + return ERROR; + } + } + argv[argc++] = copy_p; + } + continue; + } + + /* a space */ + if (*str_p == '+') { + *copy_p++ = ' '; + continue; + } + + /* no binary character, than it is normal */ + if (*str_p != '%') { + *copy_p++ = *str_p; + continue; + } + + str_p++; + + if (*str_p >= 'a' && *str_p <= 'f') + val = 10 + *str_p - 'a'; + else if (*str_p >= 'A' && *str_p <= 'F') + val = 10 + *str_p - 'A'; + else if (*str_p >= '0' && *str_p <= '9') + val = *str_p - '0'; + else + continue; + + str_p++; + + if (*str_p >= 'a' && *str_p <= 'f') + val = val * 16 + (10 + *str_p - 'a'); + else if (*str_p >= 'A' && *str_p <= 'F') + val = val * 16 + (10 + *str_p - 'A'); + else if (*str_p >= '0' && *str_p <= '9') + val = val * 16 + (*str_p - '0'); + else + str_p--; + + *copy_p++ = (char)val; + } + + ret = process_args(args, argc, argv); + + free(copy); + free(argv); + + if (ret == NOERROR) + return NOERROR; + else + return ERROR; +} + +/* + * Processes arguments sent in via the QUERY_STRING environmental + * variable that a web-server might send to program in ARG0. Returns + * 0 on noerror else -1. + */ +EXPORT int argv_web_process(argv_t *args, const char *arg0) +{ + char *env, *work = NULL; + int ret, len; + + if (! enabled) + argv_startup(); + + env = getenv("REQUEST_METHOD"); + if (env != NULL && strcmp(env, "POST") == 0) { + env = getenv("CONTENT_LENGTH"); + if (env != NULL) { + len = atoi(env); + if (len > 0) { + work = (char *)malloc(len + 1); + if (work == NULL) { + if (argv_error_stream != NULL) + (void)fprintf(argv_error_stream, + "%s: memory error during argument processing\n", + argv_program); + if (argv_interactive) + (void)exit(EXIT_CODE); + return ERROR; + } + (void)read(STDIN, work, len); + work[len] = NULLC; + } + } + } + + if (work == NULL) { + env = getenv("QUERY_STRING"); + + /* if it is not set or empty, then nothing to do */ + if (env == NULL || *env == NULLC) { + work = (char *)malloc(1); + if (work == NULL) { + if (argv_error_stream != NULL) + (void)fprintf(argv_error_stream, + "%s: memory error during argument processing\n", + argv_program); + if (argv_interactive) + (void)exit(EXIT_CODE); + return ERROR; + } + work[0] = NULLC; + } + else { + work = string_copy(env); + if (work == NULL) + return ERROR; + } + } + + ret = argv_web_process_string(args, arg0, work, "&="); + free(work); + + if (ret == NOERROR) + return NOERROR; + else + return ERROR; +} + +/* + * Print the standard usage messages for argument array ARGS (if null + * then an empty array is used). WHICH chooses between long or short + * messages (see argv.h). + * + * NOTE: if this is called before argv_process then the program name may + * be messed up. + */ +EXPORT int argv_usage(const argv_t *args, const int which) +{ + if (! enabled) + argv_startup(); + + if (process_env() != NOERROR) + return ERROR; + + if (args == NULL) + args = empty; + + if (which == ARGV_USAGE_SHORT) + usage_short(args, 0); + else if (which == ARGV_USAGE_LONG) + usage_long(args); + else + /* default/env settings */ + do_usage(args, global_usage); + + return NOERROR; +} + +/* + * See if ARG argument was used in a previous call to argv_process on + * ARGS. Returns 1 if yes else 0. + */ +EXPORT int argv_was_used(const argv_t *args, const char arg) +{ + const argv_t *arg_p; + + if (! enabled) + argv_startup(); + + for (arg_p = args; arg_p->ar_short_arg != ARGV_LAST; arg_p++) + if (arg_p->ar_short_arg == arg) { + if (arg_p->ar_type & ARGV_FLAG_USED) + return 1; + else + return 0; + } + + return 0; +} + +/* + * Frees up any allocations in ARGS that may have been done by + * argv_process. This should be done at the end of the program or + * after all the arguments have been referenced. + */ +EXPORT void argv_cleanup(const argv_t *args) +{ + const argv_t *arg_p; + int entry_c; + + if (! enabled) + argv_startup(); + + if (args == NULL) + return; + + /* run through the argument structure */ + for (arg_p = args; arg_p->ar_short_arg != ARGV_LAST; arg_p++) { + /* handle any arrays */ + if (arg_p->ar_type & ARGV_ARRAY) { + argv_array_t *arr_p = (argv_array_t *)arg_p->ar_variable; + + /* free any entries */ + if (arr_p->aa_entryn > 0) { + if (ARGV_TYPE(arg_p->ar_type) == ARGV_CHARP) { + for (entry_c = 0; entry_c < arr_p->aa_entryn; entry_c++) + free(ARGV_ARRAY_ENTRY(*arr_p, char *, entry_c)); + } + free(arr_p->aa_entries); + } + arr_p->aa_entries = NULL; + arr_p->aa_entryn = 0; + continue; + } + + /* handle individual charps */ + if (arg_p->ar_type & ARGV_FLAG_USED + && ARGV_TYPE(arg_p->ar_type) == ARGV_CHARP) { + free(*(char **)arg_p->ar_variable); + continue; + } + } +} + +/* + * Copy all the args (after the 0th), one after the other, into BUF of + * MAX_SIZE. Returns 0 on no error else -1. + * + * NOTE: you can get the 0th argument from argv_argv[0]. + */ +EXPORT int argv_copy_args(char *buf, const int max_size) +{ + char **argv_p, *buf_p = buf, *arg_p; + int argc, size_c = max_size; + + if (! enabled) + argv_startup(); + + if (process_env() != NOERROR) + return ERROR; + + if (max_size == 0) + return NOERROR; + + *buf_p = NULLC; + + if (argv_argv == NULL || max_size == 1) + return NOERROR; + + for (argv_p = argv_argv + 1, argc = 1; argc < argv_argc; argv_p++, argc++) { + + if (size_c < 2) + break; + + if (argv_p > argv_argv + 1) { + *buf_p++ = ' '; + size_c--; + } + + for (arg_p = *argv_p; *arg_p != NULLC && size_c >= 2; size_c--) + *buf_p++ = *arg_p++; + } + + *buf_p = NULLC; + return NOERROR; +}