/*
 * config.c  -  Read configuration files
 *
 * Copyright (C) 1997-2003 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.5 2003/03/09 00:43:11 gkminix Exp $
 */

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



/*
 * Variables private to this module
 */
static int curline;			/* current line in input file */
static int errors;			/* number of errors found */
static char *inbuf = NULL;		/* input buffer pointer */
static char *curpos = NULL;		/* current write position in buffer */
static int inbufsize = 0;		/* input buffer size */



/*
 * Put a character at the end of the input buffer
 */
static void putbuf(c)
char c;
{
#define BUFSIZE 1024

  int i;
  char *cp;

  /* Allocate new line buffer if necessary */
  if (inbuf == NULL) {
	inbuf = curpos = (char *)nbmalloc(BUFSIZE);
	inbufsize = BUFSIZE;
  }

  /* Check if we have to start with a fresh buffer */
  if (curpos == NULL)
	curpos = inbuf;

  /* Check that we don't get at the end of the input buffer */
  i = (curpos - inbuf);
  if (i >= (inbufsize - 1)) {
	cp = (char *)nbmalloc(inbufsize + BUFSIZE);
	memcpy(cp, inbuf, inbufsize);
	free(inbuf);
	inbuf = cp;
	inbufsize += BUFSIZE;
	curpos = inbuf + i;
  }

  /* Finally put new character into line buffer */
  *curpos++ = c;

#undef BUFSIZE
}



/*
 * Read a line from the setup file
 */
static char *readline(fd)
FILE *fd;
{
  int inquote = FALSE;
  int backslash = FALSE;
  int c = 0;
  char *cp;

  /* Check for EOF on input file */
  if (feof(fd))
	return(NULL);

  /* Read one line */
  while (TRUE) {
	/* Reset input line buffer write pointer */
	curpos = NULL;

	/* Read one line */
	while (TRUE) {
		if ((c = fgetc(fd)) == EOF)
			break;
		if (backslash) {
			/*
			 * If preceding character was a backslash, we need
			 * special handling
			 */
			switch (c) {
				case 'n':
					putbuf('\n');
					break;
				case 'r':
					putbuf('\r');
					break;
				case 't':
					putbuf('\t');
					break;
				case '\n':
					/* Ignore newline */
					curline++;
					break;
				default:
					putbuf(c);
					break;
			}
			backslash = FALSE;
			continue;
		}
		if (inquote) {
			/*
			 * Within quotes we handle all characters verbatim
			 * and don't do any processing except backslash which
			 * has already been taken care of
			 */
			if (inquote && c == '"')
				inquote = FALSE;
			if (c == '\n')
				curline++;
			putbuf(c);
			continue;
		}
		if (c == '#') {
			/*
			 * When encountering a comment sign, skip the rest of
			 * the line until newline
			 */
			while ((c = fgetc(fd)) != '\n' && c != EOF)
				;
			if (c == EOF)
				break;
			curline++;
			continue;
		}
		/*
		 * Check for end of line and special characters, and then
		 * put the character into the input buffer
		 */
		if (c == '\n') {
			curline++;
			break;
		} else if (c == '"') {
			inquote = TRUE;
			putbuf(c);
		} else if (c == '\\') {
			backslash = TRUE;
		} else
			putbuf(c);
	}
	putbuf('\0');

	/* Remove trailing whitespace */
	cp = curpos - 2;
	while (cp >= inbuf && (*cp == ' ' || *cp == '\t'))
		cp--;
	*(++cp) = '\0';

	/* Terminate reading if not an empty line */
	if (cp > inbuf) {
		/* Skip leading whitespace */
		for (cp = inbuf; *cp && (*cp == ' ' || *cp == '\t'); cp++)
			;
		return(cp);
	} else if (c == EOF)
		return(NULL);
  }
}



/*
 * Decode one line of the configuration file
 */
static void decode(params, buf)
struct paramdef *params;
char *buf;
{
  char *cp, **cpp;
  char *arg;
  int i, j;

  /* Find equal sign and isolate parameter name */
  if ((cp = strchr(buf, '=')) == NULL) {
	prnerr1("[%d] invalid line", curline);
	errors++;
	return;
  }
  arg = cp + 1;
  for (--cp; cp > buf && (*cp == ' ' || *cp == '\t'); cp--)
	;
  *(++cp) = '\0';

  /* Find parameter name in list */
  for (i = 0; params[i].name != NULL; i++)
	if (!strcmp(buf, params[i].name))
		break;
  if (params[i].name == NULL) {
	prnerr1("[%d] invalid parameter", curline);
	errors++;
	return;
  }

  /* Decode argument */
  while (*arg && (*arg == ' ' || *arg == '\t'))
	arg++;
  switch (params[i].type) {
	case par_string:
		if (arg[0] == '"') {
			j = strlen(arg);
			if (arg[j - 1] != '"') {
				prnerr1("[%d] missing end quote", curline);
				errors++;
			} else {
				arg[j - 1] = '\0';
				arg++;
			}
		}
		copystr(params[i].valptr.strptr, arg);
		break;
	case par_bool:
		if (!strcmp(arg, "true") || !strcmp(arg, "TRUE"))
			*(params[i].valptr.boolptr) = BOOL_TRUE;
		else if (!strcmp(arg, "false") || !strcmp(arg, "FALSE"))
			*(params[i].valptr.boolptr) = BOOL_FALSE;
		else {
			prnerr1("[%d] invalid boolean argument", curline);
			errors++;
		}
		break;
	case par_int:
		j = 0;
		if (arg[0] == '0' && toupper(arg[1]) == 'X') {
			arg += 2;
			for (cp = arg; *cp; cp++)
				if (!isxdigit(*cp))
					break;
			if (!*cp)
				j = sscanf(arg, "%x", params[i].valptr.intptr);
		} else {
			if (*arg == '+') {
				arg++;
				cp = arg;
			} else if (*arg == '-') {
				cp = arg + 1;
			} else {
				cp = arg;
			}
			while (*cp)
				if (!isdigit(*(cp++)))
					break;
			if (!*cp)
				j = sscanf(arg, "%d", params[i].valptr.intptr);
		}
		if (j != 1) {
			prnerr1("[%d] invalid short integer argument", curline);
			errors++;
		}
		break;
	case par_long:
		j = 0;
		if (arg[0] == '0' && toupper(arg[1]) == 'X') {
			arg += 2;
			for (cp = arg; *cp; cp++)
				if (!isxdigit(*cp))
					break;
			if (!*cp)
				j = sscanf(arg, "%lx", params[i].valptr.longptr);
		} else {
			if (*arg == '+') {
				arg++;
				cp = arg;
			} else if (*arg == '-') {
				cp = arg + 1;
			} else {
				cp = arg;
			}
			while (*cp)
				if (!isdigit(*(cp++)))
					break;
			if (!*cp)
				j = sscanf(arg, "%ld", params[i].valptr.longptr);
		}
		if (j != 1) {
			prnerr1("[%d] invalid long integer argument", curline);
			errors++;
		}
		break;
	case par_enum:
		/* Find argument in enumeration list */
		cpp = params[i].enumlist;
		assert(cpp != NULL);
		for (j = 1; *cpp != NULL; cpp++, j++)
			if (!strcmp(*cpp, arg))
				break;
		/* Use position number in list as value */
		if (*cpp != NULL)
			*(params[i].valptr.enumptr) = j;
		else {
			prnerr2("[%d] invalid argument <%s>", curline, arg);
			errors++;
		}
		break;
	case par_proc:
		if ((cp = (params[i].valptr.procptr)(params[i].name, arg)) != NULL) {
			prnerr2("[%d] %s", curline, cp);
			errors++;
		}
		break;
	case par_null:
	default:
		break;
  }
}



/*
 * Find section name in section list
 */
static struct sectdef *findsect(sectlist, name)
struct sectdef *sectlist;
const char *name;
{
  struct sectdef *cursect;
  int len;

  /* Scan through section list */
  for (cursect = sectlist; cursect->name != NULL; cursect++) {
	len = strlen(cursect->name);
	if (cursect->name[len - 1] == '*') {
		len--;
		if (!strncmp(cursect->name, name, len)) {
			if (!name[len]) {
				prnerr1("[%d] missing subsection name",
								curline);
				errors++;
			} else if (strchr(&(name[len]), ':') != NULL) {
				prnerr1("[%d] too many subsection names",
								curline);
				errors++;
			} else
				return(cursect);
		}
	} else if (!strcmp(cursect->name, name))
		return(cursect);
  }
  return(NULL);
}



/*
 * Read configuration file
 */
void readconfig(sects, fname)
struct sectdef *sects;
char *fname;
{
  struct sectdef *cursect = NULL;
  char *sectname = NULL;
  char *buf, *cp;
  FILE *fd;

  /* Nothing to do if we cannot open the setup file */
  if (fname == NULL || (fd = fopen(fname, "r")) == NULL)
	return;
  curline = 0;
  errors = 0;

  /* Read all lines in input file */
  while ((buf = readline(fd)) != NULL) {
	if (*buf == '[') {
		/* Terminate previous section */
		if (cursect != NULL && cursect->endsect != NULL) {
			cp = (cursect->endsect)(sectname, cursect);
			if (cp != NULL) {
				prnerr2("[%d] %s", curline, cp);
				errors++;
			}
		}

		/* Clear old section name */
		if (sectname != NULL) {
			free(sectname);
			sectname = NULL;
		}

		/* Isolate section name and find section record */
		if ((cp = strchr(buf, ']')) == NULL) {
			prnerr1("[%d] unterminated section name", curline);
			errors++;
			cursect = NULL;
		} else {
			buf++;
			if (*(cp + 1)) {
				prnerr1("[%d] junk after section name",
								curline);
				errors++;
			}
			*cp = '\0';
			copystr(&sectname, buf);
			cursect = findsect(sects, sectname);
		}

		/* Startup new section */
		if (cursect != NULL && cursect->startsect != NULL) {
			cp = (cursect->startsect)(sectname, cursect);
			if (cp != NULL) {
				prnerr2("[%d] %s", curline, cp);
				errors++;
			}
		}
	} else if (sectname == NULL) {
		prnerr1("[%d] definition outside of section", curline);
		errors++;
	} else if (cursect != NULL)
		decode(cursect->params, buf);
  }
  if (cursect != NULL && cursect->endsect != NULL) {
	cp = (cursect->endsect)(sectname, cursect);
	if (cp != NULL) {
		prnerr2("[%d] %s", curline, cp);
		errors++;
	}
  }

  /* Free all memory and report about errors */
  if (errors > 0) {
	prnerr2("found %d errors in file %s, terminating", errors, fname);
	exit(EXIT_CONFIG);
  }
  if (inbuf != NULL) {
	free(inbuf);
	inbuf = NULL;
  }
  if (sectname != NULL)
	free(sectname);
  fclose(fd);
}



/*
 * Read a section from a database
 */
void readdb(sect, fname)
struct sectdef *sect;
char *fname;
{
  struct sectdef *cursect = NULL;
  int foundsect = FALSE;
  char *sectname = NULL;
  char *buf, *cp;
  FILE *fd;

  /* Nothing to do if we cannot open the database file */
  if (fname == NULL) {
	prnerr0("no database file specified in configuration file");
	exit(EXIT_NODB);
  }
  if ((fd = fopen(fname, "r")) == NULL) {
	prnerr1("error opening database file %s", fname);
	exit(EXIT_NODB);
  }
  curline = 0;
  errors = 0;

  /* Read all lines in input file */
  while ((buf = readline(fd)) != NULL) {
	if (*buf == '[') {
		/* Terminate previous section */
		if (cursect != NULL && cursect->endsect != NULL)
			(cursect->endsect)(sectname, cursect);

		/* Clear old section name */
		if (sectname != NULL) {
			free(sectname);
			sectname = NULL;
		}

		/* Isolate section name and check if section found */
		cursect = NULL;
		if ((cp = strchr(buf, ']')) == NULL) {
			prnerr1("[%d] unterminated section name", curline);
			errors++;
		} else {
			buf++;
			if (*(cp + 1)) {
				prnerr1("[%d] junk after section name",
								curline);
				errors++;
			}
			*cp = '\0';
			copystr(&sectname, buf);
			if (!strcmp(sectname, sect->name)) {
				if (foundsect) {
					prnerr2("[%d] section <%s> multiply defined",
							curline, sectname);
					errors++;
				} else {
					cursect = sect;
					foundsect = TRUE;
				}
			}
		}

		/* Startup new section */
		if (cursect != NULL && cursect->startsect != NULL)
			(cursect->startsect)(sectname, cursect);
	} else if (sectname == NULL) {
		prnerr1("[%d] definition outside of section", curline);
		errors++;
	} else if (cursect != NULL)
		decode(cursect->params, buf);
  }
  if (cursect != NULL && cursect->endsect != NULL)
	(cursect->endsect)(sectname, cursect);

  /* Free all memory and report about errors */
  if (errors > 0) {
	prnerr2("found %d errors in file %s, terminating", errors, fname);
	exit(EXIT_DB);
  }
  if (!foundsect) {
	prnerr1("section <%s> not found in database", sect->name);
	exit(EXIT_NOSYS);
  }
  if (inbuf != NULL) {
	free(inbuf);
	inbuf = NULL;
  }
  if (sectname != NULL)
	free(sectname);
  fclose(fd);
}



syntax highlighted by Code2HTML, v. 0.9.1