/*
 * config.c  -  Read configuration file
 *
 * Copyright (C) 1997-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: config.c,v 1.16 2007/01/06 18:31:38 gkminix Exp $
 */

#include <common.h>
#include <nblib.h>
#include "privlib.h"
#include "privdb.h"
#include "parsefile.h"



/*
 * Private variables
 */
static int errors;				/* number of errors */
static struct parseinfo *configfile;		/* config file contents */




/*
 * Syntax error handler for parsefile()
 */
static void errorfile __F((msg, fname, lineno),
				const char *msg AND
				const char *fname AND
				int lineno)
{
  prnerr("[%s:%d] %s", fname, lineno, msg);
  errors++;
}



/*
 * Syntax error handler for parsesect()
 */
static void errorsect __F((msg, recname, item),
				const char *msg AND
				const char *recname AND
				const struct dbitem *item)
{
  struct parserec *rec;
  struct parseitem *pi;
  char *fname = "unknown";
  int lineno = 0;

  /* Find item or record in parse file description */
  rec = configfile->reclist;
  while (rec != NULL) {
	if (item != NULL) {
		pi = rec->parselist;
		while (pi != NULL && pi->data != item)
			pi = pi->next;
		if (pi != NULL) {
			if (pi->loc != NULL) {
				fname = pi->loc->fname;
				lineno = pi->lineno;
			}
			break;
		}
	} else if (!strcmp(rec->name, recname)) {
		if (rec->loc != NULL) {
			fname = rec->loc->fname;
			lineno = rec->lineno;
		}
		break;
	}
	rec = rec->next;
  }

  /* Print error message */
  errorfile(msg, fname, lineno);
}



/*
 * Merge the items of two records together into one record
 */
static void mergeitems __F((old, new),
				struct parserec *old AND
				struct parserec *new)
{
  /* Just for safety */
  if (old == NULL || new == NULL)
	return;

  /*
   * Find the last data item in the old record, and append the item
   * list of the new record.
   */
  if (old->datalist == NULL) {
	assert(old->datalast == NULL);
	old->datalist = new->datalist;
  } else {
	assert(old->datalast != NULL);
	old->datalast->next = new->datalist;
  }
  old->datalast = new->datalast;
  new->datalist = NULL;

  /* Do the same with the parse meta-data item list */
  if (old->parselist == NULL) {
	assert(old->parselast == NULL);
	old->parselist = new->parselist;
  } else {
	assert(old->parselast != NULL);
	old->parselast->next = new->parselist;
  }
  old->parselast = new->parselast;
  new->parselist = NULL;

  /* Adjust number of items */
  old->itemnum += new->itemnum;
}



/*
 * Sort configuration file and merge all sections with identical names.
 * This ensures that each section name only appears once in the list
 * of sections.
 */
static void sortconfig __F_NOARGS
{
  struct parserec *prev, *next, *cur;

  /* Nothing to sort? */
  if (configfile == NULL || configfile->reclist == NULL)
	return;

  /* Scan through record list, and merge all identical records */
  cur = configfile->reclist;
  while (cur != NULL) {
	assert(cur->name != NULL);
	prev = cur;
	next = cur->next;
	while (next != NULL) {
		assert(next->name != NULL);
		if (!strcmp(cur->name, next->name)) {
			mergeitems(cur, next);
			cur->lineno = next->lineno;
			cur->loc = next->loc;
			prev->next = next->next;
			free(next->name);
			free(next);
			next = prev->next;
			continue;
		}
		prev = next;
		next = next->next;
	}
	cur = cur->next;
  }
}



/*
 * Read configuration file
 */
int nblib_readconfig __F((sects, fname),
				const struct sectdef *sects AND
				const char *fname)
{
  static struct sectdef sectbuf[2] = {
	{ NULL, NULL, NULL, NULL},
	{ NULL, NULL, NULL, NULL}
  };
  struct parserec *rec;
  FILE *fd;
  int i;

  /*
   * Read the configuration file. If it can't be found that's not an
   * error because we should be able to run all netboot programs with-
   * out a configuration file.
   */
  if (fname == NULL || (fd = fopen(fname, "r")) == NULL)
	return(TRUE);
  configfile = nblib_parse_file(fd, fname, &errorfile);
  fclose(fd);

  /* Check for errors */
  if (configfile == NULL || nblib_parse_error != PARSESTATUS_SUCCESS) {
	if (nblib_parse_error == PARSESTATUS_READERR)
		nberror(EXIT_CONFIG,
			"read error while processing configuration file");
	else if (nblib_parse_error == PARSESTATUS_SYNTAXERR && errors > 0)
		nberror(EXIT_CONFIG,
			"%d syntax error(s) in configuration file", errors);
	else
		nberror(EXIT_CONFIG,
			"unknown error while reading configuration file");
	return(FALSE);
  }

  /* Sort configuration file contents */
  sortconfig();

  /*
   * Process each record in config file. We scan all records for each
   * section (instead of processing all sections for each record), so
   * that it is possible to resolve forward references in the configu-
   * ration file.
   */
  for (i = 0; sects[i].name != NULL; i++) {
	sectbuf[0] = sects[i];
	rec = configfile->reclist;
	while (rec != NULL) {
		(void)nblib_parse_sect(rec->name, rec->datalist,
							sectbuf, &errorsect);
		rec = rec->next;
	}
  }

  /* Delete all configuration file contents */
  nblib_parse_free(configfile);

  /* Check for errors */
  if (errors > 0) {
	nberror(EXIT_CONFIG, "%d errors in configuration file %s",
							errors, fname);
	return(FALSE);
  }
  return(TRUE);
}

