/*
 * user.c  -  user interface for makerom bootrom configuration utility
 *
 * Copyright (C) 1995-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: user.c,v 1.27 2007/02/01 12:09:22 gkminix Exp $
 */

#include <common.h>
#include <nblib.h>
#include <system/bootrom.h>
#include "makerom.h"
#include "doconfig.h"
#include "protini.h"
#include "console.h"
#include "md5.h"



/*
 * Local declarations
 */
#define DEFAULTINT	0x62		/* default packet driver interrupt */
#define MAXEXECSIZE	(128L * 1024L)	/* maximum size of DOS program */
#define PKTWATCHSIZE	2048L		/* stack size for pktwatch */



/*
 * Variables local to this module
 */
static struct bootdef bootd;




/*
 **************************************************************************
 *
 *		Ask user about parts of bootrom
 *
 **************************************************************************
 */

/*
 * Let the user select a bootrom kernel.
 */
static void getkernel __F_NOARGS
{
  int useint18 = FALSE;
  int senduid = TRUE;
  int nobbs = FALSE;
  int bootask = FALSE;
  int asktime = 0;

  /* Initialize boot definition structure */
  memzero(&bootd, sizeof(bootd));

  /* Ask user about bootrom kernel options */
  useint18 = getyn("Do you want the BIOS to look for boot disks", useint18, 1);
  if (!useint18) {
	bootask = getyn("Do you want the bootrom to ask before booting "
						"from network", bootask, 2);
	if (bootask)
		asktime = getsel("How many seconds should the bootrom wait "
						"asking (0 = forever)",
						0, 31, asktime, 3, 0);
  }
  nobbs = getyn("Do you want to disable BBS support", nobbs, 31);
  senduid = getyn("Do you want the bootrom to send a client ID", senduid, 35);
  printf("\n\n");

  /* Generate bootrom definition */
  copystr(&bootd.kernelname, config.files[FILE_KERNEL]);
  copystr(&bootd.loaders[0].name, config.files[FILE_ROM]);
  copystr(&bootd.loaders[1].name, config.files[FILE_FLOPPY]);
  bootd.kernel_flags = (senduid ? 0 : KRNFLG_NOUID);
  bootd.loadernum = 2;
  bootd.canflash = FALSE;
  bootd.loaders[0].outname = NULL;		/* set later */
  bootd.loaders[0].outtype = OUT_NONE;		/* set later */
  bootd.loaders[0].outsize = 0;
  bootd.loaders[0].useint18 = useint18;
  bootd.loaders[0].nobbs = nobbs;
  bootd.loaders[0].bootask = bootask;
  bootd.loaders[0].asktime = asktime;
  bootd.loaders[0].devnum = 0;			/* set later */
  bootd.loaders[1].outname = NULL;		/* set later */
  bootd.loaders[1].outtype = OUT_NONE;		/* set later */
  bootd.loaders[1].outsize = 0;
  bootd.loaders[1].useint18 = TRUE;
  bootd.loaders[1].nobbs = FALSE;
  bootd.loaders[1].bootask = FALSE;
  bootd.loaders[1].asktime = 0;
  bootd.loaders[1].devnum = 0;			/* set later */

  /* Check that all files are accessible */
  if (bootd.kernelname == NULL) {
	prnerr("unable to access bootrom kernel file");
	nbexit(EXIT_ACCESS);
  }
  if (bootd.loaders[0].name == NULL) {
	prnerr("unable to access rom loader file");
	nbexit(EXIT_ACCESS);
  }
  if (bootd.loaders[1].name == NULL) {
	prnerr("unable to access floppy loader file");
	nbexit(EXIT_ACCESS);
  }
}



/*
 * Get type of output file
 */
static void getouttype __F_NOARGS
{
  static const char *outdesc[OUT_MAX - OUT_MIN + 1] = {
		"Raw binary",
		"Intel hex",
		"Motorola hex",
		"Tektronix hex",
		"Image to include into a system BIOS",
		"Image for programming a flash EPROM"
  };

  int outtype, i;

  /* Ask user about output type */
  outtype = -1;
  do {
	printf("Available output file types for rom image:\n");
	for (i = OUT_MIN; i <= OUT_MAX; i++) {
		if (!bootd.canflash && i == OUT_FLASH)
			printf("(%d)\tImage for programming a FlashCard\n", i);
		else
			printf("(%d)\t%s\n", i, outdesc[i - OUT_MIN]);
	}
	outtype = getsel("Select the format you wish to use",
						OUT_MIN, OUT_MAX, OUT_DEF,
						4, (OUT_MAX - OUT_MIN + 3));
  } while (outtype < OUT_MIN || outtype > OUT_MAX);
  printf("\n\n");

  /* Set values in boot definition structure */
  bootd.loaders[0].outtype = outtype;
  switch (outtype) {
	case OUT_FLASH:
		copystr(&(bootd.loaders[0].outname), "image.flash");
		break;
	case OUT_IHEX:
	case OUT_MHEX:
	case OUT_THEX:
		copystr(&(bootd.loaders[0].outname), "image.hex");
		break;
	case OUT_BIOS:
		copystr(&(bootd.loaders[0].outname), "image.bios");
		break;
	case OUT_BINARY:
	default:
		copystr(&(bootd.loaders[0].outname), "image.rom");
		break;
  }
  copystr(&(bootd.loaders[1].outname), "image.flo");
  bootd.loaders[1].outtype = OUT_BINARY;
}




/*
 **************************************************************************
 *
 *			Selection of network card
 *
 **************************************************************************
 */

/*
 * Generate list of network card manufacturer names
 */
static struct listitem *genmanlist __F_NOARGS
{
  struct mandesc *mp;
  struct filedesc *fp;
  struct listitem *ip;
  struct listitem *itemlist;

  /* Scan through list of manufacturer names */
  itemlist = NULL;
  mp = config.manlist;
  while (mp != NULL) {
	fp = config.drvdesc;
	while (fp != NULL) {
		if (fp->manufacturer == mp)
			break;
		fp = fp->next;
	}
	if (fp != NULL) {
		ip = (struct listitem *)nbmalloc(sizeof(struct listitem));
		ip->desc = (voidstar)mp;
		ip->descstr = mp->name;
		ip->status = NULL;
		ip->next = itemlist;
		itemlist = ip;
	}
	mp = mp->next;
  }

  /* Check if we have any manufacturers at all */
  if (itemlist == NULL)
	return(NULL);

  /* Setup entry for unknown manufacturer */
  ip = (struct listitem *)nbmalloc(sizeof(struct listitem));
  ip->desc = NULL;
  ip->descstr = "Unknown manufacturer";
  ip->status = NULL;
  ip->next = itemlist;
  return(ip);
}



/*
 * Generate list of network card descriptions
 */
static struct listitem *gencardlist __F((mp), const struct mandesc *mp)
{
  struct filedesc *fp;
  struct drvdesc *drvp;
  struct listitem *ip;
  struct listitem *itemlist;
  int havedriver[NETDRV_NUM];
  unsigned int i;

  /* Check which network driver interfaces are available */
  for (i = DRVTYPE_MIN; i <= DRVTYPE_MAX; i++)
	havedriver[i] = (config.netdrv[i].filename != NULL);

  /* Scan through list of available network cards */
  itemlist = NULL;
  fp = config.drvdesc;
  while (fp != NULL) {
	if (mp == NULL || fp->manufacturer == mp) {
		drvp = fp->drvlist;
		while (drvp != NULL) {
			if (havedriver[drvp->type] &&
			    drvp->filename != NULL)
				break;
			drvp = drvp->next;
		}
		if (drvp == NULL) {
			drvp = fp->altdrvlist;
			while (drvp != NULL) {
				if (havedriver[drvp->type] &&
				    drvp->filename != NULL)
					break;
				drvp = drvp->next;
			}
		}
		ip = (struct listitem *)nbmalloc(sizeof(struct listitem));
		ip->desc = (voidstar)fp;
		ip->descstr = (fp->description != NULL ?
						fp->description : fp->name);
		ip->status = (drvp != NULL ? "*" : NULL);
		ip->next = itemlist;
		itemlist = ip;
	}
	fp = fp->next;
  }

  /* Check if we have any network cards at all */
  if (itemlist == NULL)
	return(NULL);

  /* Setup entry for unknown network card */
  ip = (struct listitem *)nbmalloc(sizeof(struct listitem));
  ip->desc = NULL;
  ip->descstr = "Unknown network card";
  ip->status = NULL;
  ip->next = itemlist;
  return(ip);
}



/*
 * Let the user select the bus type
 */
static int getbustype __F((fp), const struct filedesc *fp)
{
  int sel;

  /* If we have a bus type already, don't need to ask */
  if (fp != NULL && fp->bustype != BUSTYPE_NONE)
	return(fp->bustype);

  /* Otherwise print a list of bus types and let the user select */
  sel = -1;
  do {
	printf("\n\nKnown network card bus types:\n");
	printf("(%d)\tISA bus (including PnP)\n", BUSTYPE_ISA);
	printf("(%d)\tEISA bus\n", BUSTYPE_EISA);
	printf("(%d)\tMCA bus\n", BUSTYPE_MCA);
	printf("(%d)\tPCI bus\n", BUSTYPE_PCI);
	sel = getsel("Select the bus type of your network card",
			BUSTYPE_MIN, BUSTYPE_MAX, BUSTYPE_ISA, 5, 7);
  } while (sel < BUSTYPE_MIN || sel > BUSTYPE_MAX);
  return(sel);
}



/*
 * Let the user select a network card
 */
static void getnetcard __F((fdp), struct filedesc **fdp)
{
  struct filedesc *fp = NULL;
  struct mandesc *mp = NULL;
  struct listitem *itemlist;
  char *pnpdevid;
  int bustype, devnum;

  /* Print list of known manufacturer names */
  if ((itemlist = genmanlist()) != NULL) {
	mp = (struct mandesc *)getlistsel(
				"List of known network card manufacturers:",
				"Select a manufacturer (-1 to review list)",
				itemlist, NULL, TRUE, 6);
	printf("\n\n");
  }

  /* Print list of known network cards and let the user decide */
  if ((itemlist = gencardlist(mp)) != NULL) {
	fp = (struct filedesc *)getlistsel(
				"List of known network cards:",
				"Select a network card (-1 to review list)",
				itemlist, NULL, TRUE, 7);
	if (fp != NULL)
		printf("\nYou selected: %s\n", (fp->description != NULL ?
						fp->description : fp->name));
	else
		printf("\n\n");
  }

  /* Print out a warning message for option 0 */
  if (fp == NULL) {
	printf("\nYou did not select a specific network card. ");
	printf("Entering the network card parameters\n");
	printf("individually is not recommended. ");
	if (!getyn("Do you really want to continue", FALSE, 8))
		nbexit(EXIT_SIGNAL);
  }

  /* Get the bus type and PCI/PnP vendor/device ID for the network card */
  bustype = getbustype(fp);
  if (bustype == BUSTYPE_PCI) {
	devnum = getsel("Enter PCI device number (0 = default action)",
							0, 7, 0, 32, 0);
	bootd.loaders[0].devnum = devnum;
	bootd.loaders[1].devnum = devnum;
	if (fp == NULL || fp->pci_vendid == 0)
		bootd.pci_vendid = getnum("Enter PCI vendor ID",
							0, 0xffff, TRUE, 9);
	else
		bootd.pci_vendid = fp->pci_vendid;
	if (fp == NULL || fp->pci_devid == 0)
		bootd.pci_devid = getnum("Enter PCI device ID",
							0, 0xffff, TRUE, 10);
	else
		bootd.pci_devid = fp->pci_devid;
  } else if (fp == NULL || fp->pnp_devid == NULL) {
	while (TRUE) {
		pnpdevid = getstring("Enter PnP device ID string "
							"(maybe empty)", 11);
		if (pnpdevid == NULL || (int)strlen(pnpdevid) == 0) {
			pnpdevid = NULL;
			break;
		}
		if ((int)strlen(pnpdevid) != PNPIDLEN ||
		    !isalpha((int)(pnpdevid[0]))  ||
		    !isalpha((int)(pnpdevid[1]))  ||
		    !isalpha((int)(pnpdevid[2]))  ||
		    !isxdigit((int)(pnpdevid[3])) ||
		    !isxdigit((int)(pnpdevid[4])) ||
		    !isxdigit((int)(pnpdevid[5])) ||
		    !isxdigit((int)(pnpdevid[6])))
			printf("  Invalid ID string, it has to look like: "
								"ABC1234\n");
		else
			break;
		pnpdevid[0] = toupper(pnpdevid[0]);
		pnpdevid[1] = toupper(pnpdevid[1]);
		pnpdevid[2] = toupper(pnpdevid[2]);
	}
	if (pnpdevid != NULL)
		copystr(&(bootd.pnp_devid), pnpdevid);
  } else
	copystr(&(bootd.pnp_devid), fp->pnp_devid);
  bootd.bus_type = bustype;

  /* Check if we can offer the flash EPROM option to the user */
  bootd.canflash = checkflash(&bootd);

  /* Prepare return value */
  *fdp = fp;
  printf("\n\n");
}




/*
 **************************************************************************
 *
 *		Selection of network driver
 *
 **************************************************************************
 */

/*
 * List of available network driver interface types
 */
static const struct dtypedesc {
	char	*longdesc;
	char	*shortdesc;
} dtypes[3] = {
	{ "packet driver interface",       "packet driver" },
	{ "NDIS driver interface",         "NDIS driver"   },
	{ "generic UNDI driver interface", "UNDI driver"   }
};



/*
 * Find a driver in the list of predefined driver definitions
 */
static struct drvdesc *findnetdrv __F((fp, filename, drvtype),
				const struct filedesc *fp AND
				char *filename AND
				int drvtype)
{
  struct drvdesc *dp, *dpall, *dpmd5;
  char *basename, *cp;
  char md5[MD5_DIGEST_SIZE * 2 + 1];
  __u8 inbuf[BLKSIZE];
  __u8 digest[MD5_DIGEST_SIZE];
  size_t rdsize;
  int infile;
  unsigned int i;

  /* Check if any network card selected */
  if (fp == NULL || filename == NULL)
	return(NULL);

  /* Determine base name of driver file */
  if ((basename = strrchr(filename, '/')) != NULL)
	basename++;
  else
	basename = filename;
  if (!*basename)
	return(NULL);

  /* Open binary driver file */
  if ((infile = open(filename, O_RDONLY | O_BINARY)) < 0) {
	prnerr("unable to open driver file %s", filename);
	nbexit(EXIT_OPEN);
  }

  /* Compute the file MD5 checksum */
  md5init();
  do {
	rdsize = nbread(inbuf, BLKSIZE, infile);
	md5update(inbuf, rdsize);
  } while (rdsize > 0);
  (void)close(infile);
  md5final(digest);

  /* Convert MD5 digest into text string */
  for (i = 0; i < MD5_DIGEST_SIZE; i++)
	sprintf(&(md5[i * 2]), "%02x", (unsigned int)(digest[i]));

  /* Now find the file in the driver description list */
  dpall = NULL;
  dpmd5 = NULL;
  dp = fp->drvlist;
  i = 0;
  while (dp != NULL) {
	if (dp->type == drvtype && !strcmp(dp->basename, basename)) {
		if (dp->md5sum == NULL) {
			if (dpall == NULL)
				dpall = dp;
		} else if (!strcmp(dp->md5sum, md5)) {
			if (dpmd5 == NULL)
				dpmd5 = dp;
		}
	}
	dp = dp->next;
	if (dp == NULL && i == 0) {
		dp = fp->altdrvlist;
		i++;
	}
  }
  dp = (dpmd5 != NULL ? dpmd5 : dpall);
  if (dp == NULL || dp->descript == NULL)
	return(NULL);

  /* Ask the user if this is the correct driver description */
  printf("Your driver appears in the database with this description:\n\n");
  printf("\t%s\n\n", dp->descript);
  if (!getyn("Is this correct", TRUE, 33))
	dp = NULL;
  printf("\n");

  /* Ask the user if the driver should get installed */
  if (dp != NULL && dp->filename == NULL &&
      config.netdrvdir != NULL && config.md5fname != NULL) {
	if (getyn("Do you want this driver to get installed permanently",
								TRUE, 34)) {
		cp = instdrv(filename, drvtype);
		if (cp == NULL) {
			prnerr("unable to install network driver file %s",
								filename);
			nbexit(EXIT_MAKEROM_INSTDRV);
		}
		dp->filename = cp;
	}
	printf("\n");
  }
  return(dp);
}



/*
 * Generate list of selectable network drivers
 */
static struct listitem *gendrvlist __F((fp), struct filedesc *fp)
{
  struct drvdesc *drvp;
  struct listitem *ip;
  struct listitem *itemlist;
  int usealt, havedriver[NETDRV_NUM];
  unsigned int i;

  /* Check if we have any drivers at all */
  if (fp == NULL)
	return(NULL);

  /* Check which network driver interfaces are available */
  for (i = DRVTYPE_MIN; i <= DRVTYPE_MAX; i++)
	havedriver[i] = (config.netdrv[i].filename != NULL);

  /* Scan through list of available network drivers */
  itemlist = NULL;
  if (fp->drvlist != NULL) {
	usealt = FALSE;
	drvp = fp->drvlist;
  } else {
	usealt = TRUE;
	drvp = fp->altdrvlist;
  }
  while (drvp != NULL) {
	if (havedriver[drvp->type] && drvp->filename != NULL) {
		ip = (struct listitem *)nbmalloc(sizeof(struct listitem));
		ip->desc = (voidstar)drvp;
		ip->descstr = (drvp->descript != NULL ? drvp->descript :
								drvp->filename);
		ip->status = dtypes[drvp->type - DRVTYPE_MIN].shortdesc;
		ip->next = itemlist;
		itemlist = ip;
	}
	drvp = drvp->next;
	if (drvp == NULL && !usealt) {
		usealt = TRUE;
		drvp = fp->altdrvlist;
	}
  }

  /* Check if we have any drivers at all */
  if (itemlist == NULL)
	return(NULL);

  /* Setup entry for unlisted network driver */
  ip = (struct listitem *)nbmalloc(sizeof(struct listitem));
  ip->desc = NULL;
  ip->descstr = "Network driver not listed";
  ip->status = NULL;
  ip->next = itemlist;
  return(ip);
}



/*
 * Scan through the network driver list and determine the number of
 * bits in the options field. Then select the interface with the least
 * number of bits, e.g. that one with the least number of options to
 * be entered by the user.
 */
static struct listitem *checkdefdrv __F((itemlist), struct listitem *itemlist)
{
  struct listitem *def;
  struct drvdesc *dp;
  unsigned int j, k, bitnum;

  def = NULL;
  bitnum = 16;
  while (itemlist != NULL) {
	dp = (struct drvdesc *)(itemlist->desc);
	if (dp != NULL) {
		k = 0;
		switch (dp->type) {
			case DRVTYPE_PD:
				k = dp->drv.pd.options;
				break;
			case DRVTYPE_NDIS:
				k = dp->drv.ndis.options;
				break;
			case DRVTYPE_UNDI:
				/* there is no options value for UNDI drivers */
				k = 0;
				break;
			default:
				/* should never happen */
				k = 16;
				break;
		}
		j = 0;
		while (k > 0) {
			if ((k & 1) != 0)
				j++;
			k >>= 1;
		}
		if (j <= bitnum) {
			bitnum = j;
			def = itemlist;
		}
	}
	itemlist = itemlist->next;
  }
  return(def);
}



/*
 * Generate list of selectable network driver interfaces
 */
static struct listitem *geniflist __F_NOARGS
{
  struct listitem *ip;
  struct listitem *itemlist;
  unsigned int i;

  itemlist = NULL;
  for (i = DRVTYPE_MAX; i >= DRVTYPE_MIN; i--)
	if (config.netdrv[i].filename != NULL) {
		ip = (struct listitem *)nbmalloc(sizeof(struct listitem));
		ip->desc = (voidstar)&(dtypes[i - DRVTYPE_MIN]);
		ip->descstr = dtypes[i - DRVTYPE_MIN].longdesc;
		ip->status = NULL;
		ip->next = itemlist;
		itemlist = ip;
	}
  return(itemlist);
}



/*
 * Let the user select a network driver for the interface card selected. If
 * none has been selected just ask for the network driver interface type.
 */
static struct drvdesc *getnetdrv __F((fp), struct filedesc *fp)
{
  static struct drvdesc ddesc;
  struct drvdesc *dp;
  struct dtypedesc *tp;
  struct listitem *itemlist;
  struct listitem *def;
  unsigned int i;

  /* If we have preselected a network card, print a list of available drivers */
  if (fp != NULL) {
	if ((itemlist = gendrvlist(fp)) != NULL) {
		def = checkdefdrv(itemlist);
		dp = (struct drvdesc *)getlistsel(
				"List of drivers for this network card:",
				"Select a driver",
				itemlist, def, TRUE, 12);
		if (dp != NULL)
			return(dp);
	}
  }

  /*
   * If we got here a network card has not been selected or there are no
   * suitable network drivers available. We therefore ask about the type of
   * network driver interface and then return an empty static driver description
   * record.
   */

  /* Determine all available interface types, and let the user select */
  if ((itemlist = geniflist()) == NULL) {
	prnerr("no network driver interfaces available");
	nbexit(EXIT_MAKEROM_IFFND);
  }
  tp = (struct dtypedesc *)getlistsel(
		"The following network driver interfaces are available:",
		"Select the interface you wish to use",
		itemlist, NULL, FALSE, 13);
  i = 0;
  while (i < 3 && tp != &(dtypes[i]))
	i++;
  assert(i < 3);

  /* Setup driver description record */
  memzero(&ddesc, sizeof(ddesc));
  ddesc.type = i + DRVTYPE_MIN;
  switch (ddesc.type) {
	case DRVTYPE_PD:
		ddesc.drv.pd.minsize = -2L;
		ddesc.drv.pd.maxsize = -2L;
		break;
	case DRVTYPE_NDIS:
		ddesc.drv.ndis.minsize = -2L;
		ddesc.drv.ndis.maxsize = -2L;
		break;
	case DRVTYPE_UNDI:
	default:
		break;
  }
  return(&ddesc);
}



/*
 * Get network driver options
 */
static char *getnetopt __F((str, opt), const char *str AND int opt)
{
  long hwirq = 0L;
  long ioaddr = 0L;
  long basemem = 0L;
  long dmanum = 0L;
  long auitype = 0L;
  char *cp, *retbuf, buf[128], c;
  size_t retbuflen, retbufsize;
  size_t len;

  /* Ask the user for required driver values */
  if (opt) {
	printf("Enter network driver options:\n");
	if (opt & HW_IRQ)
		hwirq = getnum("  Hardware IRQ number", 2, 15, FALSE, 14);
	if (opt & IO_ADDR)
		ioaddr = getnum("  I/O address", 0, 0xffff, TRUE, 15);
	if (opt & BASE_MEM)
		basemem = getnum("  Shared memory address",
							0, 0xffff, TRUE, 16);
	if (opt & DMA_NUM)
		dmanum = getnum("  DMA number", 0, 7, FALSE, 17);
	if (opt & AUI_TYPE)
		auitype = getnum("  AUI type (-1 for default)",
							-1, 1, FALSE, 18);
  }
  if (auitype < 0L)
	auitype = 65535L;

  /* Generate return string */
  retbuf =  NULL;
  retbuflen = retbufsize = 0;
  while (*str) {
	if (*str != '%') {
		buf[0] = *str;
		buf[1] = '\0';
	} else {
		if (!(c = *(++str)))
			break;
		switch (c) {
			case 'S':
				sprintf(buf, "%x", DEFAULTINT);
				break;
			case 's':
				sprintf(buf, "%d", DEFAULTINT);
				break;
			case 'H':
				sprintf(buf, "%lx", hwirq);
				break;
			case 'h':
				sprintf(buf, "%ld", hwirq);
				break;
			case 'A':
				sprintf(buf, "%lx", ioaddr);
				break;
			case 'a':
				sprintf(buf, "%ld", ioaddr);
				break;
			case 'M':
				sprintf(buf, "%lx", basemem);
				break;
			case 'm':
				sprintf(buf, "%ld", basemem);
				break;
			case 'D':
				sprintf(buf, "%lx", dmanum);
				break;
			case 'd':
				sprintf(buf, "%ld", dmanum);
				break;
			case 't':
				if (auitype > 0)
					sprintf(buf, "%ld", auitype);
				break;
			default:
				buf[0] = c;
				buf[1] = '\0';
				break;
		}
	}
	str++;
	len = roundup((unsigned int)(retbuflen + strlen(buf) + 1), 32U);
	if (retbufsize < len) {
		cp = (char *)nbmalloc(len);
		if (retbuf != NULL) {
			strcpy(cp, retbuf);
			free(retbuf);
		}
		retbuf = cp;
		retbufsize = len;
	}
	cp = &retbuf[retbuflen];
	strcpy(cp, buf);
	retbuflen += strlen(buf);
  }
  return(retbuf);
}



/*
 * Ask the user about DOS program execution sizes
 */
static void getsizes __F((minsize, maxsize, needmin),
				long *minsize AND
				long *maxsize AND
				int needmin)
{
  long smin, smax;

  while (TRUE) {
	/*
	 * The sizes are defined as follows:
	 *
	 *   minimum size  -  Size of the program after becoming resident.
	 *                    If the program doesn't get resident this
	 *                    number should be zero. The default is that
	 *                    the program becomes resident, and it's size
	 *                    resident size is the same as the load size.
	 *
	 *   maximum size  -  Size of the program when it gets loaded and
	 *                    before it gets started. The default is deter-
	 *                    mined lateron by the binary patch routines.
	 *                    For EXE programs, the size is in the EXE
	 *                    header. COM programs always get 64kB.
	 */
	smin = getnum("Enter minimum execution size in bytes (-1 = default)",
						-1L, MAXEXECSIZE, FALSE, 19);
	smax = getnum("Enter maximum execution size in bytes (-1 = default)",
						-1L, MAXEXECSIZE, FALSE, 20);
	if (smax == 0L)
		printf("Maximum size is not allowed to be zero\n");
	else if (needmin && smin == 0L)
		printf("Minimum size is not allowed to be zero\n");
	else if (smin > 0L && smax > 0L && smin > smax)
		printf("Minimum size is larger than maximum size\n");
	else
		break;
  }
  *minsize = smin;
  *maxsize = smax;
}



/*
 * Ask the user about additional DOS programs to execute
 */
static int getuserprog __F((pdp), struct progdef *pdp)
{
  int i;
  int drvindex = 0;
  char *pathname = NULL;
  char *cmdline = NULL;
  char *pattern = NULL;
  char *buf;
  long minsize, maxsize;

  copystr(&pattern, "*.com:*.exe");
  while (getyn("Do you want to specify an additional program", FALSE, 21)) {
	pathname = getfilename("Enter path name of the program",
						config.utilsdir, pattern, 22);
	buf = getstring("Enter command line for the program", 23);
	if (*buf)
		copystr(&cmdline, buf);
	getsizes(&minsize, &maxsize, FALSE);
	if (getyn("Should the program run before the network driver",
								FALSE, 24)) {
		for (i = pdp->prognum; i > drvindex; i--) {
			pdp->prognames[i] = pdp->prognames[i - 1];
			pdp->progargs[i] = pdp->progargs[i - 1];
			pdp->minsizes[i] = pdp->minsizes[i - 1];
			pdp->maxsizes[i] = pdp->maxsizes[i - 1];
		}
		drvindex++;
	} else
		i = pdp->prognum;
	pdp->prognames[i] = pathname;
	pdp->progargs[i] = cmdline;
	pdp->minsizes[i] = minsize;
	pdp->maxsizes[i] = maxsize;
	pdp->prognum++;
  }
  free(pattern);
  return(drvindex);
}



/*
 * Ask the user about relevant information for packet drivers
 */
static struct drvdesc *getpktdrvinfo __F((dp, fp),
				struct drvdesc *dp AND
				struct filedesc *fp)
{
  struct pktdrvdef *defp = &bootd.netdrv.driverdefs.pd;
  struct drvdesc *dpt;
  int options = 0;
  long minsize = -2L;
  long maxsize = -2L;
  char *filename = NULL;
  char *cmdline = NULL;

  /* Get filename of packet driver binary */
  assert(dp != NULL);
  filename = dp->filename;
  if (filename == NULL) {
	filename = getfilename("Enter path of packet driver",
					config.netdrv[DRVTYPE_PD].searchdir,
					config.netdrv[DRVTYPE_PD].patlist,
					25);
	dpt = findnetdrv(fp, filename, DRVTYPE_PD);
	if (dpt != NULL) {
		dp = dpt;
		filename = dp->filename;
	}
  }
  assert(filename != NULL);
  defp->progs.prognames[0] = filename;
  defp->progs.prognum = 1;

  /* Get info from description record */
  cmdline = dp->drv.pd.cmdline;
  options = dp->drv.pd.options;
  minsize = dp->drv.pd.minsize;
  maxsize = dp->drv.pd.maxsize;

  /* Ask user for command line options */
  if (cmdline == NULL) {
	cmdline = getstring("Enter command line for packet driver [0x%S]", 26);
	if (!*cmdline)
		cmdline = "0x%S";
  }
  defp->progs.progargs[0] = getnetopt(cmdline, options);

  /* Ask user about execution sizes */
  if (minsize < -1L || maxsize < -1L || maxsize == 0)
	getsizes(&minsize, &maxsize, TRUE);
  defp->progs.minsizes[0] = minsize;
  defp->progs.maxsizes[0] = maxsize;

  /* Ask the user if to include the packet driver debugger program */
  filename = NULL;
  copystr(&filename, "pktwatch.com");
  checkaccess(&filename, config.utilsdir, ACCESS_FILE_READ);
  if (filename != NULL &&
      getyn("Do you want to use the packet driver debugger", FALSE, 27)) {
	long l = filesize(filename);

	if (l == -1)
		nbexit(-1);

	defp->progs.prognames[defp->progs.prognum] = filename;
	defp->progs.progargs[defp->progs.prognum] = NULL;
	defp->progs.minsizes[defp->progs.prognum] = l;
	defp->progs.maxsizes[defp->progs.prognum] =
		defp->progs.minsizes[defp->progs.prognum] + PKTWATCHSIZE;
	defp->progs.prognum++;
  }

  /* Ask the user to include additional programs */
  defp->drvindex = getuserprog(&(defp->progs));
  return(dp);
}



/*
 * Ask the user about relevant information for NDIS drivers
 */
static struct drvdesc *getndisinfo __F((dp, fp),
				struct drvdesc *dp AND
				struct filedesc *fp)
{
  struct ndisdef *defp = &bootd.netdrv.driverdefs.ndis;
  struct drvdesc *dpt;
  int options = 0;
  long minsize = -2L;
  long maxsize = -2L;
  char *filename = NULL;
  char *protini = NULL;

  /* Get filename of NDIS driver binary */
  assert(dp != NULL);
  filename = dp->filename;
  if (filename == NULL) {
	filename = getfilename("Enter path of NDIS driver",
					config.netdrv[DRVTYPE_NDIS].searchdir,
					config.netdrv[DRVTYPE_NDIS].patlist,
					25);
	dpt = findnetdrv(fp, filename, DRVTYPE_NDIS);
	if (dpt != NULL) {
		dp = dpt;
		filename = dp->filename;
	}
  }
  assert(filename != NULL);
  defp->progs.prognames[0] = filename;
  defp->progs.progargs[0] = NULL;
  defp->progs.prognum = 1;

  /* Get info from description record */
  protini = dp->drv.ndis.protini;
  options = dp->drv.ndis.options;
  minsize = dp->drv.ndis.minsize;
  maxsize = dp->drv.ndis.maxsize;

  /* Ask user about execution sizes */
  if (minsize < -1L || maxsize < -1L || maxsize == 0)
	getsizes(&minsize, &maxsize, TRUE);
  defp->progs.minsizes[0] = minsize;
  defp->progs.maxsizes[0] = maxsize;

  /* Ask for protocol.ini file and driver options and normalize protocol.ini */
  filename = NULL;
  if (protini != NULL && !strncmp(protini, "file:", 5))
	copystr(&filename, &(protini[5]));
  if (protini == NULL || (filename != NULL && !*filename))
	filename = getfilename("Enter path name of PROTOCOL.INI file",
						config.netdrvdir, "*.ini", 28);
  if (filename != NULL) {
	char *cp;

	cp = readprotini(filename);
	defp->protocolini = getnetopt(cp, options);
	free(filename);
	free(cp);
  } else {
	defp->protocolini = getnetopt(protini, options);
  }
  if (protini != NULL)
	free(protini);

  /* Ask the user to include additional program */
  defp->drvindex = getuserprog(&(defp->progs));
  return(dp);
}



/*
 * Ask the user about relevant information for UNDI drivers
 */
static struct drvdesc *getundiinfo __F((dp, fp),
				struct drvdesc *dp AND
				struct filedesc *fp)
{
  struct undidef *defp = &bootd.netdrv.driverdefs.undi;
  struct drvdesc *dpt;
  char *filename = NULL;

  /* Get filename of UNDI driver binary */
  assert(dp != NULL);
  filename = dp->filename;
  if (filename == NULL) {
	filename = getfilename("Enter path name of UNDI driver",
					config.netdrv[DRVTYPE_UNDI].searchdir,
					config.netdrv[DRVTYPE_UNDI].patlist,
					25);
	dpt = findnetdrv(fp, filename, DRVTYPE_UNDI);
	if (dpt != NULL) {
		dp = dpt;
		filename = dp->filename;
	}
  }
  assert(filename != NULL);
  defp->name = filename;
  return(dp);
}



/*
 * Let the user select a network driver and get all relevant information for
 * the network driver.
 */
static void getdrvinfo __F((fp), struct filedesc *fp)
{
  struct drvdesc *dp;

  /* Let the user select the network driver */
  dp = getnetdrv(fp);
  assert(dp != NULL && dp->type >= DRVTYPE_MIN && dp->type <= DRVTYPE_MAX);
  copystr(&bootd.netdrv.name, config.netdrv[dp->type].filename);
  bootd.netdrv.drivertype = dp->type;

  /* Let the user enter all relevant information for the network driver */
  switch (dp->type) {
	case DRVTYPE_PD:
		dp = getpktdrvinfo(dp, fp);
		break;
	case DRVTYPE_NDIS:
		dp = getndisinfo(dp, fp);
		break;
	case DRVTYPE_UNDI:
		dp = getundiinfo(dp, fp);
		break;
  }
  printf("\n\n");

  /* Set driver flags */
  bootd.netdrv.driverflags = dp->flags;
}




/*
 **************************************************************************
 *
 *			Ask user about database name
 *
 **************************************************************************
 */


/*
 * Ask the user about a system name to add to the systems database
 */
static void getdbname __F_NOARGS
{
  char *buf;

  /* No database add if debugging */
  if (debug)
	return;

  /* Ask about system name */
  if (getyn("Do you want to add these definitions to the systems database",
								FALSE, 29)) {
	do {
		buf = getstring("Enter system name", 30);
		if (!*buf || strcspn(buf, ":*?") != strlen(buf))
			buf = NULL;
	} while (buf == NULL);
	copystr(&(bootd.name), buf);
  }
  printf("\n\n");
}




/*
 **************************************************************************
 *
 *			Main routine
 *
 **************************************************************************
 */

/*
 * Main routine which handles all user input
 */
struct bootdef *getuser __F_NOARGS
{
  struct filedesc *fp;

  /* Read MD5 checksum files and driver database */
  readmd5();
  dodrvdb();
  clearmd5();

  /* Handle user I/O */
  initconsole(config.helpfile);
  getkernel();
  getnetcard(&fp);
  getdrvinfo(fp);
  getouttype();
  getdbname();
  closeconsole();
  return(&bootd);
}

