/*
 * drvdb.c  -  Read network driver configuration data
 *
 * Copyright (C) 2003-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: drvdb.c,v 1.13 2007/01/06 18:31:12 gkminix Exp $
 */

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



/*
 * Maximum execution size
 */
#define MAX_EXEC_SIZE	(128L * 1024L)



/*
 * Default path and file name definitions
 */
#define PKTDRVFILE	"netpkt.bin"
#define NDISFILE	"netndis.bin"
#define UNDIFILE	"netundi.bin"



/*
 * Local variables
 */
static int errors = 0;			/* number of errors */



/*
 * Error handler for database routines
 */
static void drvdberr __F((dbname, msg, class),
				const char *dbname AND
				const char *msg AND
				dberrclass class)
{
  char *classmsg = NULL;

  /* Print error message */
  switch (class) {
	case DBERR_CLASS_WARNING:
		classmsg = "Warning";
		break;
	case DBERR_CLASS_ERROR:
		errors++;
		classmsg = "Error";
		break;
	case DBERR_CLASS_FATAL:
		errors++;
		classmsg = "Fatal";
		break;
	case DBERR_CLASS_INFO:
		prnlog(LOGLEVEL_NOTICE, "Info: [%s] %s\n", dbname, msg);
		/* Fall through */
	default:
		classmsg = NULL;
		break;
  }
  if (classmsg != NULL)
	prnerr("%s: [%s] %s", classmsg, dbname, msg);

  /* Terminate in case of fatal error */
  if (class == DBERR_CLASS_FATAL)
	nbexit(EXIT_MAKEROM_DRVDB);
}



/*
 * Assign a new string to a string pointer
 */
static inline void assignstr __F((old, new), char **old AND char **new)
{
  if (*new != NULL) {
	if (*old != NULL)
		free(*old);
	*old = *new;
	*new = NULL;
  }
}



/*
 * Read network driver interface information from database
 */
static const struct {
	char   *name;
	int     type;
} dsections[] = {
  { "pktdrv",	DRVTYPE_PD },
  { "ndis",	DRVTYPE_NDIS },
  { "undi",	DRVTYPE_UNDI },
  { NULL,	DRVTYPE_NONE }
};

static struct {
	char *filename;
	char *description;
	char *directory;
	char *pattern;
} netdrv_vars;

static struct paramdef netdrv_params[] = {
  { "filename",		par_file,	NULL,	{&netdrv_vars.filename}},
  { "description",	par_string,	NULL,	{&netdrv_vars.description}},
  { "directory",	par_string,	NULL,	{&netdrv_vars.directory}},
  { "pattern",		par_string,	NULL,	{&netdrv_vars.pattern}},
  { NULL,		par_null,	NULL,	{NULL}}
};


static char *start_netdrv_sect __F((sectname, cursect),
				const char *sectname AND
				struct sectdef **cursect)
{
  int i = -1;
  char *cp;

  /* Clear temporary variable structure */
  memzero(&netdrv_vars, sizeof(netdrv_vars));

  /* Check that the section name is valid */
  if ((cp = strrchr(sectname, ':')) != NULL && *(++cp) != '\0')
	for (i = 0; dsections[i].name != NULL; i++) {
		if (!strcmp(cp, dsections[i].name))
			break;
	}

  /* Check if we have to process this section */
  if (i == -1 || dsections[i].name == NULL)
	*cursect = NULL;

  /* Return without error */
  return(NULL);
}


static char *end_netdrv_sect __F((sectname, cursect),
				const char *sectname AND
				struct sectdef **cursect)
{
  struct netdrvinfo *ip;
  int drvtype, i;
  char *cp;

  /* Determine name of section, e.g. driver interface type */
  assert((cp = strrchr(sectname, ':')) != NULL);
  cp++;
  drvtype = DRVTYPE_NONE;
  for (i = 0; dsections[i].name != NULL; i++)
	if (!strcmp(cp, dsections[i].name)) {
		drvtype = dsections[i].type;
		break;
	}
  assert(drvtype >= DRVTYPE_MIN && drvtype <= DRVTYPE_MAX);

  /* Add new item to info list */
  ip = &config.netdrv[drvtype];
  assignstr(&(ip->descript), &(netdrv_vars.description));
  assignstr(&(ip->searchdir), &(netdrv_vars.directory));
  assignstr(&(ip->patlist), &(netdrv_vars.pattern));
  assignstr(&(ip->filename), &(netdrv_vars.filename));
  return(NULL);
}



/*
 * List of recognized options for network driver binary files
 */
static const struct {
	char *optname;
	int   optval;
} netoptlist[] = {
	{ "HW_IRQ",	HW_IRQ },
	{ "IO_ADDR",	IO_ADDR },
	{ "BASE_MEM",	BASE_MEM },
	{ "AUI_TYPE",	AUI_TYPE },
	{ "DMA_NUM",	DMA_NUM },
	{ NULL,		0 }
};


static int getoption __F((optstr), const char *optstr)
{
  const char *cp;
  int optval = 0;
  size_t i, len;

  cp = optstr;
  while (cp != NULL) {
	cp += strspn(cp, " \t");
	len = strcspn(cp, " \t,");
	if (len > 0) {
		for (i = 0; netoptlist[i].optname != NULL; i++)
			if (strlen(netoptlist[i].optname) == len &&
			    !strncmp(netoptlist[i].optname, cp, len))
				break;
		if (netoptlist[i].optname == NULL)
			return(-1);
		optval |= netoptlist[i].optval;
	}
	if ((cp = strchr(cp, ',')) != NULL)
		cp++;
  }
  return(optval);
}



/*
 * Find a network driver description in the configuration file structure. If
 * none is found, a new entry is generated and inserted into the description
 * list.
 */
static struct filedesc *getfiledesc __F((sectname, makenew),
				const char *sectname AND
				int makenew)
{
  struct filedesc *fp = config.drvdesc;
  char *cp;

  /* Determine driver name from section name */
  assert((cp = strchr(sectname, ':')) != NULL);
  if (!*(++cp))
	return(NULL);

  /* Find driver description in list */
  while (fp != NULL) {
	if (!strcmp(fp->name, cp))
		break;
	fp = fp->next;
  }

  /* Create new description record if not found */
  if (fp == NULL && makenew) {
	fp = (struct filedesc *)nbmalloc(sizeof(struct filedesc));
	copystr(&(fp->name), cp);
	fp->next = config.drvdesc;
	config.drvdesc = fp;
  }
  return(fp);
}



/*
 * Search the list of manufacturer names
 */
static struct mandesc *findman __F((name), char **name)
{
  struct mandesc *mp;

  /* Search for name in list */
  mp = config.manlist;
  while (mp != NULL) {
	if (!strcmp(mp->name, *name))
		break;
	mp = mp->next;
  }

  /* If not found, create a new entry */
  if (mp == NULL) {
	mp = (struct mandesc *)nbmalloc(sizeof(struct mandesc));
	mp->name = *name;
	mp->next = config.manlist;
	config.manlist = mp;
	*name = NULL;
  }
  return(mp);
}



/*
 * Read network card information from configuration file
 */
static struct enumdef btypes[] = {
  { "ISA",	BUSTYPE_ISA },
  { "EISA",	BUSTYPE_EISA },
  { "MCA",	BUSTYPE_MCA },
  { "PCI",	BUSTYPE_PCI },
  { NULL,	BUSTYPE_NONE }
};

static struct {
	char *usedriver;
	char *description;
	char *manufacturer;
	char *pnp_devid;
	int   pci_vendid;
	int   pci_devid;
	int   bustype;
} card_vars;

static struct paramdef card_params[] = {
  { "description",	par_string, NULL,	{&card_vars.description}},
  { "manufacturer",	par_string, NULL,	{&card_vars.manufacturer}},
  { "bustype",		par_enum,   btypes,	{&card_vars.bustype}},
  { "pcivendor",	par_int,    NULL,	{&card_vars.pci_vendid}},
  { "pcidevice",	par_int,    NULL,	{&card_vars.pci_devid}},
  { "pnpdevid",		par_string, NULL,	{&card_vars.pnp_devid}},
  { "usedriver",	par_string, NULL,	{&card_vars.usedriver}},
  { NULL,		par_null,   NULL,	{NULL}}
};


static char *start_card_sect __F((sectname, cursect),
				const char *sectname AND
				struct sectdef **cursect)
{
  /* Clear temporary variable structure */
  memzero(&card_vars, sizeof(card_vars));

  /* Check if section name is valid */
  if (getfiledesc(sectname, TRUE) == NULL)
	return("invalid section name");

  /* Return without error */
  return(NULL);
}


static char *end_card_sect __F((sectname, cursect),
				const char *sectname AND
				struct sectdef **cursect)
{
  struct filedesc *fp = getfiledesc(sectname, FALSE);
  char *cp = NULL;

  /* This should never happen */
  assert(fp != NULL);

  /* Check that the bus type doesn't change from a previous definition */
  if (card_vars.bustype != BUSTYPE_NONE) {
	if (fp->bustype != BUSTYPE_NONE &&
	    fp->bustype != card_vars.bustype) {
		if (cp == NULL)
			cp = "bus type already defined differently";
	} else
		fp->bustype = card_vars.bustype;
  }

  /* Check that a PCI vendor/device ID doesn't change */
  if (card_vars.pci_vendid != 0) {
	if (fp->pci_vendid != 0 &&
	    fp->pci_vendid != card_vars.pci_vendid) {
		if (cp == NULL)
			cp = "PCI vendor ID already defined differently";
	} else
		fp->pci_vendid = card_vars.pci_vendid;
  }
  if (card_vars.pci_devid != 0) {
	if (fp->pci_devid != 0 &&
	    fp->pci_devid != card_vars.pci_devid) {
		if (cp == NULL)
			cp = "PCI device ID already defined differently";
	} else
		fp->pci_devid  = card_vars.pci_devid;
  }

  /* Check that the PnP device ID is correct and doesn't change */
  if (card_vars.pnp_devid != NULL) {
	if (fp->pnp_devid != NULL &&
	    strcmp(card_vars.pnp_devid, fp->pnp_devid)) {
		if (cp == NULL)
			cp = "PnP device ID already defined differently";
	} else if ((int)strlen(card_vars.pnp_devid) > 0 &&
	           ((int)strlen(card_vars.pnp_devid) != PNPIDLEN ||
	            !isalpha((int)(card_vars.pnp_devid[0]))  ||
	            !isalpha((int)(card_vars.pnp_devid[1]))  ||
	            !isalpha((int)(card_vars.pnp_devid[2]))  ||
	            !isxdigit((int)(card_vars.pnp_devid[3])) ||
	            !isxdigit((int)(card_vars.pnp_devid[4])) ||
	            !isxdigit((int)(card_vars.pnp_devid[5])) ||
	            !isxdigit((int)(card_vars.pnp_devid[6])))) {
		if (cp == NULL)
			cp = "invalid PnP device ID string";
	} else {
		if ((int)strlen(card_vars.pnp_devid) == 0) {
			free(card_vars.pnp_devid);
			card_vars.pnp_devid = NULL;
			if (fp->pnp_devid != NULL)
				free(fp->pnp_devid);
			fp->pnp_devid = NULL;
		} else
			assignstr(&(fp->pnp_devid), &(card_vars.pnp_devid));
	}
  }

  /* Check that the bus type is correct */
  if (cp == NULL && fp->bustype != BUSTYPE_PCI &&
      (fp->pci_vendid != 0 || fp->pci_devid != 0))
	cp = "PCI vendor/device ID specified for non-PCI bus type";
  if (cp == NULL && fp->bustype == BUSTYPE_PCI &&
      fp->pnp_devid != NULL)
	cp = "PnP device ID string defined for PCI bus type";
  if (cp == NULL && fp->pnp_devid != NULL &&
      (fp->pci_vendid != 0 || fp->pci_devid != 0))
	cp = "PCI vendor/device ID and PnP device ID defined";

  /* Assign alternative driver list and card description */
  assignstr(&(fp->usedriver), &(card_vars.usedriver));
  assignstr(&(fp->description), &(card_vars.description));

  /* Assign manufacturer name */
  if (card_vars.manufacturer != NULL)
	fp->manufacturer = findman(&(card_vars.manufacturer));

  /* Clear all dynamic memory which hasn't been saved so far */
  if (card_vars.pnp_devid != NULL)
	free(card_vars.pnp_devid);
  if (card_vars.usedriver != NULL)
	free(card_vars.usedriver);
  if (card_vars.description != NULL)
	free(card_vars.description);
  if (card_vars.manufacturer != NULL)
	free(card_vars.manufacturer);
  return(cp);
}



/*
 * Read network driver information from configuration file
 */
static struct enumdef dtypes[] = {
  { "packet driver",	DRVTYPE_PD },
  { "ndis driver",	DRVTYPE_NDIS },
  { "undi driver",	DRVTYPE_UNDI },
  { NULL,		DRVTYPE_NONE }
};

static struct {
	char *filename;
	char *parameters;
	char *pd_cmdline;
	char *ndis_protini;
	char *drvdesc;
	char *md5sum;
	int   drvtype;
	int   noreset;
	int   nomode0;
	int   irqpci;
	long  minsize;
	long  maxsize;
} driver_vars;

static struct paramdef driver_params[] = {
  { "driverdesc",	par_string, NULL,	{&driver_vars.drvdesc}},
  { "file",		par_string, NULL,	{&driver_vars.filename}},
  { "params",		par_string, NULL,	{&driver_vars.parameters}},
  { "cmdline",		par_string, NULL,	{&driver_vars.pd_cmdline}},
  { "protini",		par_string, NULL,	{&driver_vars.ndis_protini}},
  { "md5sum",		par_string, NULL,	{&driver_vars.md5sum}},
  { "drvtype",		par_enum,   dtypes,	{&driver_vars.drvtype}},
  { "minsize",		par_long,   NULL,	{&driver_vars.minsize}},
  { "maxsize",		par_long,   NULL,	{&driver_vars.maxsize}},
  { "noreset",		par_bool,   NULL,	{&driver_vars.noreset}},
  { "nomode0",		par_bool,   NULL,	{&driver_vars.nomode0}},
  { "irqpci",		par_bool,   NULL,	{&driver_vars.irqpci}},
  { NULL,		par_null,   NULL,	{NULL}}
};


static char *start_driver_sect __F((sectname, cursect),
				const char *sectname AND
				struct sectdef **cursect)
{
  /* Clear temporary variable structure */
  memzero(&driver_vars, sizeof(driver_vars));
  driver_vars.noreset = BOOL_UNDEF;
  driver_vars.nomode0 = BOOL_UNDEF;
  driver_vars.irqpci = BOOL_UNDEF;
  driver_vars.minsize = -1L;
  driver_vars.maxsize = -1L;

  /* Check if section name is valid */
  if (getfiledesc(sectname, FALSE) == NULL)
	*cursect = NULL;

  /* Return without error */
  return(NULL);
}


static char *end_driver_sect __F((sectname, cursect),
				const char *sectname AND
				struct sectdef **cursect)
{
  struct filedesc *fp = getfiledesc(sectname, FALSE);
  struct drvdesc *dp;
  char *cp = NULL;
  char *mdp, *basename;
  int opt = -1;

  /* This should never happen */
  assert(fp != NULL);

  /* Check that parameter combinations are valid */
  switch (driver_vars.drvtype) {
	case DRVTYPE_NONE:
		if (driver_vars.filename != NULL ||
		    driver_vars.parameters != NULL ||
		    driver_vars.minsize != -1L ||
		    driver_vars.maxsize != -1L ||
		    driver_vars.pd_cmdline != NULL ||
		    driver_vars.ndis_protini != NULL ||
		    driver_vars.md5sum != NULL ||
		    driver_vars.drvdesc != NULL)
			cp = "missing driver type parameter";
		break;
	case DRVTYPE_PD:
		if (driver_vars.ndis_protini != NULL)
			cp = "bogus \'protini\' parameter in packet driver definition";
		break;
	case DRVTYPE_NDIS:
		if (driver_vars.pd_cmdline != NULL)
			cp = "bogus \'cmdline\' parameter in NDIS driver definition";
		else if (driver_vars.noreset != BOOL_UNDEF)
			cp = "bogus \'noreset\' parameter in NDIS driver definition";
		else if (driver_vars.nomode0 != BOOL_UNDEF)
			cp = "bogus \'nomode0\' parameter in NDIS driver definition";
		break;
	case DRVTYPE_UNDI:
		if (driver_vars.parameters != NULL)
			cp = "bogus \'params\' parameter in UNDI driver definition";
		else if (driver_vars.ndis_protini != NULL)
			cp = "bogus \'protini\' parameter in UNDI driver definition";
		else if (driver_vars.pd_cmdline != NULL)
			cp = "bogus \'cmdline\' parameter in UNDI driver definition";
		else if (driver_vars.minsize != -1L ||
		         driver_vars.maxsize != -1L)
			cp = "bogus \'minsize\' or \'maxsize\' parameter in UNDI driver definition";
		else if (driver_vars.noreset != BOOL_UNDEF)
			cp = "bogus \'noreset\' parameter in UNDI driver definition";
		else if (driver_vars.nomode0 != BOOL_UNDEF)
			cp = "bogus \'nomode0\' parameter in UNDI driver definition";
		else if (driver_vars.irqpci != BOOL_UNDEF)
			cp = "bogus \'irqpci\' parameter in UNDI driver definition";
		break;
	default:
		assert(driver_vars.drvtype >= DRVTYPE_MIN &&
		       driver_vars.drvtype <= DRVTYPE_MAX);
		break;
  }

  /* Analyze options */
  if (driver_vars.parameters != NULL) {
	if ((opt = getoption(driver_vars.parameters)) < 0 && cp == NULL)
		cp = "invalid \'params\' parameter";
	free(driver_vars.parameters);
	driver_vars.parameters = NULL;
  }

  /* Analyze execution sizes */
  if (driver_vars.minsize < -2L)
	driver_vars.minsize = -2L;
  if (driver_vars.maxsize < -2L)
	driver_vars.maxsize = -2L;
  if (cp == NULL &&
      ((driver_vars.minsize > 0L &&
        driver_vars.maxsize > 0L &&
        driver_vars.minsize > driver_vars.maxsize) ||
       driver_vars.minsize > MAX_EXEC_SIZE ||
       driver_vars.maxsize > MAX_EXEC_SIZE))
	cp = "invalid execution size values";

  /* Check for correct MD5 sum string, and convert it to lower case */
  if (driver_vars.md5sum != NULL) {
	for (mdp = driver_vars.md5sum; *mdp; mdp++) {
		*mdp = tolower(*mdp);
		if ((*mdp < '0' || *mdp > '9') &&
		    (*mdp < 'a' || *mdp > 'f'))
			break;
	}
	if (cp == NULL && (int)(mdp - driver_vars.md5sum) != MD5_SUM_LENGTH)
		cp = "invalid MD5 checksum string";
  }

  /* Determine driver binary base name */
  if (driver_vars.filename == NULL)
	basename = NULL;
  else if ((basename = strrchr(driver_vars.filename, '/')) != NULL)
	basename++;
  else
	basename = driver_vars.filename;
  if (basename != NULL && !*basename)
	basename = NULL;

  /* Add new driver description record */
  if (driver_vars.drvtype != DRVTYPE_NONE && basename != NULL) {
	dp = (struct drvdesc *)nbmalloc(sizeof(struct drvdesc));
	dp->type = driver_vars.drvtype;
	dp->flags = 0;
	copystr(&(dp->basename), basename);
	switch (driver_vars.drvtype) {
		case DRVTYPE_PD:
			if (driver_vars.noreset == BOOL_TRUE)
				dp->flags |= DRVFLG_NORESET;
			if (driver_vars.nomode0 == BOOL_TRUE)
				dp->flags |= DRVFLG_NOMODE0;
			if (driver_vars.irqpci == BOOL_TRUE)
				dp->flags |= DRVFLG_IRQPCI;
			if (opt >= 0)
				dp->drv.pd.options = opt;
			dp->drv.pd.minsize = driver_vars.minsize;
			dp->drv.pd.maxsize = driver_vars.maxsize;
			assignstr(&(dp->drv.pd.cmdline),
					&(driver_vars.pd_cmdline));
			break;
		case DRVTYPE_NDIS:
			if (driver_vars.irqpci == BOOL_TRUE)
				dp->flags |= DRVFLG_IRQPCI;
			if (opt >= 0)
				dp->drv.ndis.options = opt;
			dp->drv.ndis.minsize = driver_vars.minsize;
			dp->drv.ndis.maxsize = driver_vars.maxsize;
			assignstr(&(dp->drv.ndis.protini),
					&(driver_vars.ndis_protini));
			break;
		case DRVTYPE_UNDI:
			break;
	}
	assignstr(&(dp->descript), &(driver_vars.drvdesc));
	assignstr(&(dp->filename), &(driver_vars.filename));
	assignstr(&(dp->md5sum), &(driver_vars.md5sum));
	dp->next = fp->drvlist;
	fp->drvlist = dp;
  }

  /* Clear all dynamic memory before returning */
  if (driver_vars.filename != NULL)
	free(driver_vars.filename);
  if (driver_vars.parameters != NULL)
	free(driver_vars.parameters);
  if (driver_vars.pd_cmdline != NULL)
	free(driver_vars.pd_cmdline);
  if (driver_vars.ndis_protini != NULL)
	free(driver_vars.ndis_protini);
  if (driver_vars.md5sum != NULL)
	free(driver_vars.md5sum);
  if (driver_vars.drvdesc != NULL)
	free(driver_vars.drvdesc);
  return(cp);
}



/*
 * Sections for network driver definition database
 *
 * The order is important here!
 */
static struct sectdef driversects[] = {
  { "netdrv:*",	netdrv_params,	&start_netdrv_sect,	&end_netdrv_sect },
  { "card:*",	card_params,	&start_card_sect,	&end_card_sect },
  { "driver:*",	driver_params,	&start_driver_sect,	&end_driver_sect },
  { NULL,	NULL,		NULL,	NULL}
};



/*
 * Normalize directory and file names and cleanup the network driver
 * description list.
 */
static void donormalize __F_NOARGS
{
  struct filedesc *fp;
  struct drvdesc *dp, *dpt;
  char *sdir;
  int i;

  /* Normalize the network driver interface file names */
  for (i = DRVTYPE_MIN; i <= DRVTYPE_MAX; i++) {
	setpath(&config.netdrv[i].searchdir, config.netdrvdir);
	checkaccess(&config.netdrv[i].filename, config.bindir,
							ACCESS_FILE_READ);
  }

  /* Scan through all network cards and process their drivers */
  fp = config.drvdesc;
  while (fp != NULL) {
	/*
	 * Normalize network driver file names, check MD5 checksums and remove
	 * all drivers with the same base file name but without an MD5 checksum.
	 */
	dp = fp->drvlist;
	while (dp != NULL) {
		assert(dp->type >= DRVTYPE_MIN &&
		       dp->type <= DRVTYPE_MAX);
		sdir = NULL;
		switch (dp->type) {
			case DRVTYPE_PD:
				sdir = config.netdrv[DRVTYPE_PD].searchdir;
				break;
			case DRVTYPE_NDIS:
				sdir = config.netdrv[DRVTYPE_NDIS].searchdir;
				break;
			case DRVTYPE_UNDI:
				sdir = config.netdrv[DRVTYPE_UNDI].searchdir;
				break;
		}
		checkaccess(&(dp->filename), sdir, ACCESS_FILE_READ);
		checkmd5sum(&(dp->filename), dp->md5sum);
		assert(dp->basename != NULL);
		if (dp->filename != NULL && dp->md5sum != NULL) {
			dpt = fp->drvlist;
			while (dpt != NULL) {
				assert(dpt->basename != NULL);
				if (dpt != dp &&
				    dpt->filename != NULL &&
				    dpt->md5sum == NULL &&
				    dpt->type == dp->type &&
				    !strcmp(dpt->basename, dp->basename)) {
					free(dpt->filename);
					dpt->filename = NULL;
				}
				dpt = dpt->next;
			}
		}
		dp = dp->next;
	}
	fp = fp->next;
  }
}



/*
 * Resolve cross-reference driver lists and cleanup network card list
 */
static void doresolve __F_NOARGS
{
  struct filedesc *fp, *oldfp, *fpt;
  char *cp;

  fp = config.drvdesc;
  oldfp = NULL;
  while (fp != NULL) {
	fpt = NULL;
	if ((cp = fp->usedriver) != NULL) {
		fpt = config.drvdesc;
		while (fpt != NULL) {
			if (fpt != fp && !strcmp(fpt->name, cp))
				break;
			fpt = fpt->next;
		}
	}
	if (fpt != NULL) {
		if (fpt->altdrvlist != NULL)
			fp->altdrvlist = fpt->altdrvlist;
		else
			fp->altdrvlist = fpt->drvlist;
	}
	if (fp->drvlist == NULL && fp->altdrvlist == NULL) {
		/* Delete item from list */
		if (fp->name != NULL)
			free(fp->name);
		if (fp->description != NULL)
			free(fp->description);
		if (fp->pnp_devid != NULL)
			free(fp->pnp_devid);
		if (fp->usedriver != NULL)
			free(fp->usedriver);
		if (oldfp == NULL)
			config.drvdesc = fp->next;
		else
			oldfp->next = fp->next;
		fpt = fp;
		fp = fp->next;
		free(fpt);
	} else {
		oldfp = fp;
		fp = fp->next;
	}
  }
}



/*
 * Set configuration structure with default values
 */
static const struct {
	char **cnfptr;
	char  *defstr;
} filedefaults[] = {
	{ &config.netdrv[DRVTYPE_PD].filename,		PKTDRVFILE },
	{ &config.netdrv[DRVTYPE_NDIS].filename,	NDISFILE },
	{ &config.netdrv[DRVTYPE_UNDI].filename,	UNDIFILE },
	{ NULL,						NULL }
};

static const char *netdescstr[] = {
	"Packet Driver interface (PD)",
	"Network Driver Interface Specification (NDIS)",
	"Universal Network Driver Interface (UNDI)"
};

static const char *netpathstr[] = {
	"pktdrvr:pktdrvr/drivers",
	"ndis2:ndis2/drivers",
	"undi:undi/drivers"
};

static const char *patternstr[] = {
	"*.com:*.exe",
	"*.sys:*.dos",
	"*.undi"
};



/*
 * Read network driver database
 */
void dodrvdb __F_NOARGS
{
  DBHDL dbhandle;
  int i, found;

  /* Check if we have anything to do */
  if (config.drvdbname == NULL) {
	prnlog(LOGLEVEL_NORMAL, "Warning: No network driver database specified!");
	return;
  }

  /* Copy default file names into structure */
  for (i = 0; filedefaults[i].defstr != NULL; i++)
	copystr(filedefaults[i].cnfptr, filedefaults[i].defstr);

  /* Set defaults for network driver interface definitions */
  for (i = DRVTYPE_MIN; i <= DRVTYPE_MAX; i++) {
	copystr(&config.netdrv[i].descript, netdescstr[i - DRVTYPE_MIN]);
	copystr(&config.netdrv[i].searchdir, netpathstr[i - DRVTYPE_MIN]);
	copystr(&config.netdrv[i].patlist, patternstr[i - DRVTYPE_MIN]);
  }

  /* Generate a network driver database handle */
  dbhandle = createdb(config.drvdbname, &drvdberr);
  if (dbhandle == DBHDL_NULL)
	nbexit(-1);

  /* Open network driver database */
  errors = 0;
  if (opendb(dbhandle, TRUE) == 0 || errors > 0) {
	prnerr("unable to open network driver database");
	nbexit(EXIT_MAKEROM_DRVDB);
  }

  /* Search for all records */
  for (i = 0; driversects[i].name != NULL; i++) {
	found = findfirst(dbhandle, driversects[i].name);
	while (found) {
		(void)readrec(dbhandle, driversects);
		found = findnext(dbhandle);
	}
	if (errors > 0) {
		prnerr("error scanning network driver database");
		nbexit(EXIT_MAKEROM_DRVDB);
	}
  }

  /* Close and free everything */
  closedb(dbhandle);
  freedb(dbhandle);

  /* Normalize directory and file names and resolve cross-references */
  donormalize();
  doresolve();
}

