OSSP CVS Repository

ossp - ossp-pkg/petidomo/libargv/argv.c 1.1.1.1
Not logged in
[Honeypot]  [Browse]  [Directory]  [Home]  [Login
[Reports]  [Search]  [Ticket]  [Timeline
  [Raw

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;
}

CVSTrac 2.0.1