ossp-pkg/petidomo/libargv/argv.c
1.1.1.1
/*
* 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 <ctype.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#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;
}