/*
 * parseopt.c  -  Parse command line options
 *
 * Copyright (C) 1998-2007 Gero Kuhlmann <gero@gkminix.han.de>
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2 of the License, or
 *  any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program; if not, write to the Free Software
 *  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 *
 * $Id: parseopt.c,v 1.24 2007/01/06 18:31:38 gkminix Exp $
 */

#define NEED_GETOPT 1
#include <common.h>
#include <nblib.h>
#include "privlib.h"



/*
 * Layout of help screen. The option names start at FIRST_COLUMN and
 * the option help text starts at SECOND_COLUMN.
 */
#define FIRST_COLUMN	4
#define SECOND_COLUMN	35



/*
 * Layout of short option value
 */
#define LONG_FLAG	0x4000		/* flag if long option exists */
#define LONG_MASK	0x3fff		/* mask to remove long option flag */
#define NUM_MASK	0x3f00		/* mask to get option number */
#define CHAR_MASK	0x00ff		/* mask to get short character value */

#define NUM_SHIFT	8		/* bits to shift option number */
#define NUM_MIN		1		/* minimum option number */
#define NUM_MAX		(NUM_MASK >> NUM_SHIFT)



/*
 * Pointer to array with copyright messages
 */
static struct cmdopt *copyright = NULL;



/*
 * Global variables exported by this module
 *
 * Note that the debug flag does NOT cause debugging output (for that
 * the verbose flag should be used). Instead it should be used to trigger
 * debug operations in the netboot programs (like including a debugging
 * boot image loader in a mknbi program). The debug-mode command line
 * option is not printed in the help screen for this reason.
 */
int verbose = 0;			/* verbosity flag */
int quiet = FALSE;			/* quiet flag */
int debug = FALSE;			/* debugging flag */



/*
 * Forward declarations
 */
static int helpopt = FALSE;		/* help command line option */
static int versionopt = FALSE;		/* version command line option */



/*
 * Definition of command line options common to all programs
 */
static struct cmdopt common_opts[] = {
	{ "config-file", 'C', strval, {&configname},
	  "name of configuration file", "FILE"				},
	{ "database", 'D', strval, {&sysdbname},
	  "name of system database", "FILE|DB"				},
	{ "datadir", 'N', strval, {&nbdatadir},
	  "netboot shared data directory", "DIR"			},
	{ "libdir", 'L', strval, {&nblibdir},
	  "system-dependant data directory", "DIR"			},
	{ "homedir", 'H', strval, {&nbhomedir},
	  "user-writable home directory for netboot files", "DIR"	},
	{ "log-file", 'F', strval, {&nblogname},
	  "name of log file", "FILE"					},
	{ "quiet", 'q', boolval, {&quiet},
	  "suppress error messages", NULL				},
	{ "verbose", 'x', boolval, {&verbose},
	  "increase verbosity level", NULL				},
	{ "debug-mode", '\0', boolval, {&debug},
	  NULL, NULL							},
	{ "version", 'v', boolval, {&versionopt},
	  "print version number", NULL					},
	{ "help", 'h', boolval, {&helpopt},
	  "print this help text", NULL					},
	{ NULL, '\0', noval, {NULL}, NULL, NULL				}
};



/*
 * Print version information and quit
 */
static void print_version __F_NOARGS
{
  char **msg;

  /* Print program name, version and optionally any copyright message */
  fprintf(stderr, "%s " VERSION "\n", progname);
  if (copyright != NULL && copyright->valptr.strptr != NULL) {
	msg = copyright->valptr.strptr;
	while (*msg != NULL) {
		fprintf(stderr, "%s\n", *msg);
		msg++;
	}
  }
  fprintf(stderr, "\n");

  /* Print supported runtime options */
  fprintf(stderr, "Database types supported:\n");
  fprintf(stderr, "\tText files\n");
#ifdef HAVE_BDB
  fprintf(stderr, "\tBerkeley DB\n");
#endif
#ifdef HAVE_ODBC
  fprintf(stderr, "\tSQL (via ODBC)\n");
#endif
  fprintf(stderr, "\n");
  nbexit(EXIT_SUCCESS);
}



/*
 * Print help text for one item
 */
static void print_item_help __F((pos, helptext), int pos AND const char *helptext)
{
  char *buf = NULL;
  char *cp;

  copystr(&buf, helptext);
  cp = strtok(buf, "\n");
  while (cp) {
	for ( ; pos < SECOND_COLUMN; pos++)
		putchar(' ');
	printf("%s\n", cp);
	cp = strtok(NULL, "\n");
	pos = 0;
  }
  free(buf);
}



/*
 * Print help screen and exit
 */
static void print_help __F((opts), struct cmdopt *opts)
{
  struct cmdopt *curopt;
  int i, prarg = 0;

  /* First print the usage line with all non-option arguments */
  printf("\nUsage: %s [options]", progname);
  for (i = 0, curopt = opts; curopt->valtype != noval; curopt++)
	if (curopt->valtype == nonopt) {
		printf(" [<%s>", curopt->longopt);
		i++;
	}
  for (; i > 0; i--)
	printf("]");
  printf("\n\n");

  /* Now print all non-option argument help strings */
  for (curopt = opts; curopt->valtype != noval; curopt++)
	if (curopt->valtype == nonopt) {
		for (i = 0; i < FIRST_COLUMN; i++)
			putchar(' ');
		printf("<%s>", curopt->longopt);
		i += strlen(curopt->longopt) + 2;
		print_item_help(i, curopt->helptext);
		prarg++;
	}
  if (prarg > 0)
	printf("\n");

  /* Finally print all options */
  printf("Options:\n");
  for (curopt = opts; curopt->valtype != noval; curopt++) {
	if (curopt->valtype != nonopt && curopt->helptext != NULL) {
		for (i = 0; i < FIRST_COLUMN; i++)
			putchar(' ');
		if ((curopt->shortopt & CHAR_MASK) == 0)
			printf("    --%s", curopt->longopt);
		else
			printf("-%c, --%s", (curopt->shortopt & CHAR_MASK),
							curopt->longopt);
		i += strlen(curopt->longopt) + 6;
		if (curopt->helparg != NULL) {
			printf("=%s", curopt->helparg);
			i += strlen(curopt->helparg) + 1;
		}
		print_item_help(i, curopt->helptext);
	}
  }
  printf("\n");
  nbexit(EXIT_USAGE);
}



/*
 * Compare two option descriptions for sorting
 */
static int cmpopt __F((entry1, entry2),
				const voidstar entry1 AND
				const voidstar entry2)
{
  struct cmdopt *opt1 = (struct cmdopt *)entry1;
  struct cmdopt *opt2 = (struct cmdopt *)entry2;
  int c1 = (opt1->shortopt & CHAR_MASK);
  int c2 = (opt2->shortopt & CHAR_MASK);

  /* Compare upper case letters only if no char is 0 */
  if (c1 != 0 && c2 != 0) {
	if (toupper(c1) < toupper(c2))
		return(-1);
	else if (toupper(c1) > toupper(c2))
		return(1);
  }

  /* Always put zero-chars at the end of the list */
  if (c1 == 0)
	c1 = 255;
  if (c2 == 0)
	c2 = 255;
  if (c1 < c2)
	return(-1);
  else if (c1 > c2)
	return(1);
  else
	return(0);
}



/*
 * Find a command line option in the common option array
 */
struct cmdopt *nblib_find_opt __F((param), const voidstar param)
{
  struct cmdopt *curopt, *ret = NULL;

  for (curopt = common_opts; curopt->valtype != noval; curopt++)
	if (curopt->valtype != nonopt && curopt->valptr.dummyptr == param) {
		ret = curopt;
		break;
	}
  return(ret);
}



/*
 * Handle option parsing
 */
void nblib_parse_opt __F((argc, argv, opts),
				int argc AND
				char **argv AND
				struct cmdopt *opts)
{
  struct cmdopt *optbuf = NULL;
  struct cmdopt *curopt, *tmpopt;
  struct option *longbuf = NULL;
  struct option *curlong;
  int islong = FALSE;
  char *cp, *shortbuf = NULL;
  int optnum, optchar;
  long intarg;

  /* This routine has to be called by the global setup routine */
  assert(progname != NULL);

  /* Determine the total number of options */
  optnum = sizeof(common_opts) / sizeof(struct cmdopt) - 1;
  if (opts != NULL)
	for (curopt = opts; curopt->valtype != noval; curopt++)
		optnum++;

  /*
   * Copy all options into one big buffer to make further handling easier.
   * First copy all named options into the buffer, sort it, and then copy
   * all non-option arguments. We have to preserve their order. Note that
   * there are no non-option arguments and zero-char options in the common
   * option list.
   */
  tmpopt = optbuf = (struct cmdopt *)nbmalloc(sizeof(struct cmdopt) * (optnum + 1));
  optnum = 0;
  if (opts != NULL)
	for (curopt = opts; curopt->valtype != noval; ) {
		if (curopt->valtype == copyrightmsg)
			copyright = curopt;
		else if (curopt->valtype != nonopt) {
			assert(optnum <= NUM_MAX);
			*tmpopt = *curopt;
			tmpopt->shortopt = (tmpopt->shortopt & CHAR_MASK) +
					((optnum + NUM_MIN) << NUM_SHIFT);
			tmpopt++;
			optnum++;
		}
		curopt++;
	}
  for (curopt = common_opts; curopt->valtype != noval; ) {
	if (curopt->valtype != nonopt) {
		assert(optnum <= NUM_MAX);
		*tmpopt = *curopt;
		tmpopt->shortopt = (tmpopt->shortopt & CHAR_MASK) +
					((optnum + NUM_MIN) << NUM_SHIFT);
		tmpopt++;
		optnum++;
	}
	curopt++;
  }
  qsort(optbuf, optnum, sizeof(struct cmdopt), &cmpopt);
  if (opts != NULL)
	for (curopt = opts; curopt->valtype != noval; ) {
		if (curopt->valtype == nonopt) {
			assert(optnum <= NUM_MAX);
			*tmpopt = *curopt;
			tmpopt->shortopt = (tmpopt->shortopt & CHAR_MASK) +
					((optnum + NUM_MIN) << NUM_SHIFT);
			tmpopt++;
			optnum++;
		}
		curopt++;
	}
  *tmpopt = *curopt;		/* Copy end marker */

  /* Setup buffer for single letter options */
  shortbuf = (char *)nbmalloc((optnum + 1) * 2);
  for (cp = shortbuf, curopt = optbuf; curopt->valtype != noval; curopt++) {
	if ((curopt->shortopt & CHAR_MASK) == '\0')
		continue;
	if (curopt->valtype == intval ||
	    curopt->valtype == strval ||
	    curopt->valtype == procval) {
		*cp++ = (curopt->shortopt & CHAR_MASK);
		*cp++ = ':';
	} else if (curopt->valtype == boolval)
		*cp++ = (curopt->shortopt & CHAR_MASK);
  }
  *cp = '\0';

  /* Setup buffer for long option names */
  longbuf = (struct option *)nbmalloc(sizeof(struct option) * (optnum + 1));
  for (curlong = longbuf, curopt = optbuf; curopt->valtype != noval; curopt++)
	if (curopt->valtype != nonopt && curopt->longopt != NULL) {
		curlong->name = curopt->longopt;
		if (curopt->valtype == intval || curopt->valtype == strval)
			curlong->has_arg = required_argument;
		else
			curlong->has_arg = no_argument;
		curlong->flag = NULL;
		curlong->val = (curopt->shortopt & LONG_MASK) | LONG_FLAG;
		curlong++;
	}
  curlong->name = NULL;

  /* Now parse all options using getopt */
  opterr = 0;
  while ((optchar = getopt_long(argc, argv, shortbuf, longbuf, NULL)) != EOF) {
	/* Check if we have a long option */
	islong = (optchar & LONG_FLAG) != 0;
	optchar &= LONG_MASK;
	/* Find option value in table */
	for (curopt = optbuf; curopt->valtype != noval; curopt++)
		if (optchar == (islong ? curopt->shortopt :
					(curopt->shortopt) & CHAR_MASK))
			break;
	/* Select action according to option */
	switch (curopt->valtype) {
		case boolval:
			(*(curopt->valptr.intptr))++;
			break;
		case intval:
			errno = 0;
			intarg = strtol(optarg, &cp, 0);
			if (*cp || errno != 0 ||
			    intarg < INT_MIN || intarg > INT_MAX) {
				fprintf(stderr, "%s: invalid numerical argument to option ",
								progname);
				if (islong)
					fprintf(stderr, "\"--%s\"\n",
						curopt->longopt);
				else
					fprintf(stderr, "\"-%c\"\n",
						curopt->shortopt & CHAR_MASK);
				nbexit(EXIT_USAGE);
			}
			*(curopt->valptr.intptr) = intarg;
			break;
		case longval:
			errno = 0;
			intarg = strtol(optarg, &cp, 0);
			if (*cp || errno != 0) {
				fprintf(stderr, "%s: invalid numerical argument to option ",
								progname);
				if (islong)
					fprintf(stderr, "\"--%s\"\n",
						curopt->longopt);
				else
					fprintf(stderr, "\"-%c\"\n",
						curopt->shortopt & CHAR_MASK);
				nbexit(EXIT_USAGE);
			}
			*(curopt->valptr.longptr) = intarg;
			break;
		case strval:
			copystr(curopt->valptr.strptr, optarg);
			break;
		case procval:
			(curopt->valptr.procptr)(curopt, optarg);
			break;
		case nonopt:
			break;
		default:
			fprintf(stderr, "%s: invalid option ", progname);
			if (!optopt) {
				if ((cp = strchr(argv[optind - 1], '=')) != NULL)
					*cp = '\0';
				fprintf(stderr, "\"%s\"\n", argv[optind - 1]);
			} else
				fprintf(stderr, "\"-%c\"\n", optopt);
			nbexit(EXIT_USAGE);
	}
	/* Check for version and help options */
	if (helpopt)
		print_help(optbuf);
	if (versionopt)
		print_version();
  }

  /* Finally parse all non-option arguments */
  curopt = NULL;
  while (optind < argc) {
	/* Find next non-option argument in option buffer */
	if (curopt == NULL)
		curopt = optbuf;
	else
		curopt++;
	for (; curopt->valtype != noval; curopt++)
		if (curopt->valtype == nonopt)
			break;
	if (curopt->valtype != nonopt) {
		fprintf(stderr, "%s: invalid argument \"%s\"\n",
						progname, argv[optind]);
		nbexit(EXIT_USAGE);
	}
	copystr(curopt->valptr.strptr, argv[optind]);
	optind++;
  }

  /* Free all occupied memory space */
  if (optbuf != NULL)
	free(optbuf);
  if (shortbuf != NULL)
	free(shortbuf);
  if (longbuf != NULL)
	free(longbuf);
}

