/*
 * parsefile.c  -  Parse a configuration or database text file
 *
 * 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: parsefile.c,v 1.13 2007/01/06 18:31:38 gkminix Exp $
 */

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



/*
 * Definitions for handling $if meta-commands
 */
#define IFLEVELS	 16		/* max. number of $if levels */
#define IFSTATE_SKIP	-1		/* skip $if command */
#define IFSTATE_FALSE	 0		/* currently in FALSE state */
#define IFSTATE_TRUE	 1		/* currently in TRUE state */



/*
 * Structure holding the info for each input file
 */
struct fdef {
	char        *fname;		/* file name */
	FILE        *fd;		/* file descriptor */
	int          curline;		/* current line number */
	int          iflevel;		/* current $if level */
	int          ifstate[IFLEVELS];	/* $if status for each level */
	struct fdef *next;		/* next file descriptor */
};



/*
 * Global error code
 */
int nblib_parse_error;



/*
 * Variables used for the text file reading routines
 */
static parseerr errorhandler;		/* error handling routine */
static struct fdef *curfile;		/* current input file */
static char *curpos;			/* current write position in buffer */
static char *inbuf = NULL;		/* input buffer pointer */
static int inbufsize = 0;		/* input buffer size */




/*
 *********************************************************************************
 *
 *		Routines for printing error messages
 *
 *********************************************************************************
 */


/*
 * Print a syntax error message. Returns TRUE if an error has been detected.
 */
static void doerror __F((msg, arg), const char *msg AND const char *arg)
{
  char *buf;

  /* Check if if have an error at all */
  if (msg == NULL)
	return;

  /* Update global error code */
  nblib_parse_error = PARSESTATUS_SYNTAXERR;

  /*
   * If we have a user provided error handler, generate the error message
   * and call the handler.
   */
  if (errorhandler != NULL) {
	if (arg != NULL) {
		buf = (char *)nbmalloc(strlen(msg) + strlen(arg) + 1);
		sprintf(buf, msg, arg);
		(*errorhandler)(buf, curfile->fname, curfile->curline);
		free(buf);
	} else
		(*errorhandler)(msg, curfile->fname, curfile->curline);
	return;
  }

  /*
   * If we don't have a user provided error handler, we have to print the
   * error message ourselves.
   */
  buf = (char *)nbmalloc(strlen(msg) + 10);
  strcpy(buf, "[%s:%d] ");
  strcat(buf, msg);
  strcat(buf, "\n");
  if (arg != NULL)
	prnerr(buf, curfile->fname, curfile->curline, arg);
  else
	prnerr(buf, curfile->fname, curfile->curline);
  free(buf);
}




/*
 *********************************************************************************
 *
 *		Routines for handling a list of file names
 *
 *		This list of file names is used to print suitable error
 *		messages later in the process when the records and items,
 *		which were read into memory, are examined.
 *
 *********************************************************************************
 */


/*
 * Check if a file name exists in the list
 */
static struct parseloc *findloc __F((fname, loc),
			const char *fname AND
			struct parseloc *loc)
{
  while (loc != NULL) {
	if (!strcmp(loc->fname, fname))
		return(loc);
	loc = loc->next;
  }
  return(NULL);
}



/*
 * Add a new file name into the list
 */
static struct parseloc *addloc __F((fname, info),
			const char *fname AND
			struct parseinfo *info)
{
  struct parseloc *p;

  if ((p = findloc(fname, info->loclist)) == NULL) {
	p = (struct parseloc *)nbmalloc(sizeof(struct parseloc));
	copystr(&(p->fname), fname);
	p->next = info->loclist;
	info->loclist = p;
  }
  return(p);
}




/*
 *********************************************************************************
 *
 *		Routines for handling include files
 *
 *********************************************************************************
 */


/*
 * Check if a file is already open
 */
static int isopen __F((fname), const char *fname)
{
  struct fdef *p;

  p = curfile;
  while (p != NULL) {
	if (p->fname != NULL && !strcmp(p->fname, fname) && p->fd != NULL)
		return(TRUE);
	p = p->next;
  }
  return(FALSE);
}



/*
 * Open a new file
 */
static void openfile __F((fname), const char *fname)
{
  struct fdef *newfile;
  FILE *fd;

  /* Check if the file is open already to avoid include loops */
  if (isopen(fname)) {
	doerror("include file %s already open", fname);
	return;
  }

  /* Open the new file */
  if ((fd = fopen(fname, "r")) == NULL) {
	doerror("unable to open file %s", fname);
	return;
  }

  /* Create a new include file list entry */
  newfile = (struct fdef *)nbmalloc(sizeof(struct fdef));
  copystr(&(newfile->fname), fname);
  newfile->fd = fd;
  newfile->curline = 0;
  newfile->iflevel = -1;
  newfile->next = curfile;
  curfile = newfile;

  /* Reset global pointers */
  curpos = NULL;
}



/*
 * Close an include file
 */
static void closefile __F_NOARGS
{
  struct fdef *p;

  if (curfile != NULL && curfile->next != NULL) {
	p = curfile;
	curfile = p->next;
	if (p->fname != NULL)
		free(p->fname);
	if (p->fd != NULL)
		(void)fclose(p->fd);
	(void)free(p);
  }
}




/*
 *********************************************************************************
 *
 *		Routines to handle meta-commands
 *
 *********************************************************************************
 */


/*
 * List of variables
 */
struct intvar {
	char          *name;		/* name of variable */
	char          *val;		/* variable value */
	struct intvar *next;		/* pointer to next variable */
};

static struct intvar *uservars = NULL;	/* list of user-defined variables */



/*
 * List of predefined variables
 */
static struct intvar predefvars[] = {
	{ "VERSION",		PACKAGE_VERSION,	NULL },
	{ "PACKAGE",		PACKAGE_NAME,		NULL },
#ifdef HAVE_BDB
	{ "HAVE_BDB",		"YES",			NULL },
#endif
#ifdef HAVE_ODBC
	{ "HAVE_SQL",		"YES",			NULL },
#endif
	{ NULL,			NULL,			NULL }
};



/*
 * Check for valid variable name
 */
static int checkvar __F((name), char *name)
{
  char *cp;

  /* Check if we have a variable name at all */
  if (name == NULL)
	return(FALSE);

  /* The name has to always start with a letter */
  if (!isalpha((int)name[0]))
	return(FALSE);

  /* Now scan all remaining characters */
  for (cp = name + 1; *cp; cp++)
	if (!isalnum((int)*cp) && *cp != '_')
		return(FALSE);

  /* The name is OK */
  return(TRUE);
}



/*
 * Find variable
 */
static char *findvar __F((name), const char *name)
{
  struct intvar *p = uservars;
  int i;

  /* First scan through user-defined variable list */
  while (p != NULL) {
	if (!strcmp(p->name, name))
		return(p->val);
	p = p->next;
  }

  /* If not found, check predefined variables */
  for (i = 0; predefvars[i].name != NULL; i++) {
	if (!strcmp(predefvars[i].name, name))
		return(predefvars[i].val);
  }

  /* The debugging flag is special */
  if (debug && !strcmp(name, "DEBUG"))
	return("YES");

  /* Otherwise try environment variable */
#ifdef HAVE_GETENV
  return(getenv(name));
#else
  return(NULL)
#endif
}



/*
 * Set variable value
 */
static void setvar __F((name, val), const char *name AND const char *val)
{
  struct intvar *p = uservars;

  /* Check if variable already defined by user */
  while (p != NULL) {
	if (!strcmp(p->name, name)) {
		/* Set new variable value */
		if (p->val != NULL) {
			free(p->val);
			p->val = NULL;
		}
		copystr(&(p->val), val);
		return;
	}
	p = p->next;
  }

  /* Create new variable */
  p = (struct intvar *)nbmalloc(sizeof(struct intvar));
  copystr(&(p->name), name);
  copystr(&(p->val), val);
  p->next = uservars;
  uservars = p;
}



/*
 * Delete variable list
 */
static void delvars __F_NOARGS
{
  struct intvar *p;

  while (uservars != NULL) {
	p = uservars;
	uservars = p->next;
	if (p->name != NULL)
		free(p->name);
	if (p->val != NULL)
		free(p->val);
	free(p);
  }
}



/*
 * Set $if status
 */
static void setif __F((val, invert), int val AND int invert)
{
  /* Check if max. level exceeded */
  if (curfile->iflevel >= (IFLEVELS - 1)) {
	doerror("$if nesting too deep", NULL);
	return;
  }

  /* Set $if state */
  curfile->iflevel++;
  if (val == IFSTATE_TRUE)
	curfile->ifstate[curfile->iflevel] =
				(invert ? IFSTATE_FALSE : IFSTATE_TRUE);
  else if (val == IFSTATE_FALSE)
	curfile->ifstate[curfile->iflevel] =
				(invert ? IFSTATE_TRUE : IFSTATE_FALSE);
  else
	curfile->ifstate[curfile->iflevel] = val;
}



/*
 * Find start of token
 */
static inline char *findstart __F((str), char *str)
{
  while (*str && (*str == ' ' || *str == '\t'))
	str++;
  return(str);
}



/*
 * Find end of token
 */
static inline char *findend __F((str), char *str)
{
  while (*str && *str != ' ' && *str != '\t')
	str++;
  return(str);
}



/*
 * Parse an argument string containing an expression. If it returns a
 * result, it will be in a new memory block which has to be freed by
 * the caller. The argument string gets modified.
 */
static char *doexpr __F((arg, endchar), char **arg AND char endchar)
{
  char *cp, c;
  char *start = *arg;
  char *ret = NULL;

  /* Check if we have to process anything */
  if (start == NULL)
	return(NULL);

  /* Check if the argument is a string value */
  if (*start == '"') {
	for (cp = ++start; *cp && *cp != '"'; cp++)
		;
	if (*cp == '"') {
		*cp = '\0';
		cp++;
	}
	*arg = cp = findstart(cp);
	copystr(&ret, start);
	return(ret);
  }
  
  /* Check if the argument is strcat function */
  if (!strncmp(start, "$strcat", 7)) {
	char *val1, *val2;
	size_t len1, len2;

	/* Concatenate two strings */
	cp = findstart(start + 7);
	if (*cp != '(') {
		doerror("missing open bracket", NULL);
		return(NULL);
	}
	cp++;
	val1 = doexpr(&cp, ',');
	*arg = cp;
	if (*cp != ',') {
		if (val1 != NULL)
			free(val1);
		doerror("missing second argument to $strcat function", NULL);
		return(NULL);
	}
	cp = findstart(cp + 1);
	val2 = doexpr(&cp, ')');
	*arg = cp;
	if (*cp != ')') {
		if (val1 != NULL)
			free(val1);
		if (val2 != NULL)
			free(val2);
		doerror("missing closing bracket", NULL);
		return(NULL);
	}
	*arg = cp = findstart(cp + 1);
	if (val1 == NULL && val2 != NULL)
		ret = val2;
	else if (val1 != NULL && val2 == NULL)
		ret = val1;
	else if (val1 != NULL && val2 != NULL) {
		len1 = strlen(val1);
		len2 = strlen(val2);
		ret = (char *)nbmalloc(len1 + len2 + 1);
		(void)strcpy(ret, val1);
		(void)strcat(ret, val2);
		free(val1);
		free(val2);
	}
	return(ret);
  }

  /* Check if the argument is strsub function */
  if (!strncmp(start, "$strsub", 7)) {
	char *val1, *val2, *val3;
	long ofs, len;

	/* Extract substring */
	cp = findstart(start + 7);
	if (*cp != '(') {
		doerror("missing open bracket", NULL);
		return(NULL);
	}
	cp++;
	val1 = doexpr(&cp, ',');
	*arg = cp;
	if (*cp != ',') {
		if (val1 != NULL)
			free(val1);
		doerror("missing offset argument to $strsub function", NULL);
		return(NULL);
	}
	cp = findstart(cp + 1);
	val2 = doexpr(&cp, ',');
	*arg = cp;
	if (*cp != ',') {
		if (val1 != NULL)
			free(val1);
		if (val2 != NULL)
			free(val2);
		doerror("missing length argument to $strsub function", NULL);
		return(NULL);
	}
	cp = findstart(cp + 1);
	val3 = doexpr(&cp, ')');
	*arg = cp;
	if (*cp != ')') {
		if (val1 != NULL)
			free(val1);
		if (val2 != NULL)
			free(val2);
		if (val3 != NULL)
			free(val3);
		doerror("missing closing bracket", NULL);
		return(NULL);
	}
	*arg = cp = findstart(cp + 1);

	ofs = 0;
	if (val2 != NULL) {
		ofs = strtol(val2, &cp, 0);
		if (*cp != '\0') {
			free(val2);
			if (val1 != NULL)
				free(val1);
			if (val3 != NULL)
				free(val3);
			doerror("offset value is not a number", NULL);
			return(NULL);
		}
		free(val2);
		if (ofs < 0)
			ofs = 0;
	}

	len = 0;
	if (val3 != NULL) {
		len = strtol(val3, &cp, 0);
		if (*cp != '\0') {
			free(val3);
			if (val1 != NULL)
				free(val1);
			doerror("length value is not a number", NULL);
			return(NULL);
		}
		free(val3);
	}

	if (val1 != NULL) {
		size_t arglen = strlen(val1);

		if ((size_t)ofs < arglen) {
			if (len >= 0) {
				if ((size_t)(ofs + len) > arglen)
					len = (long)arglen - ofs;
				val1[ofs + len] = '\0';
			}
			copystr(&ret, &(val1[ofs]));
		}
		free(val1);
	}
	return(ret);
  }

  /* Everything else is treated as a string up until endchar */
  for (cp = start; *cp && *cp != endchar; cp++)
	;
  *arg = cp;
  cp--;
  while (cp > start && (*cp == ' ' || *cp == '\t'))
	cp--;
  cp++;
  c = *cp;
  *cp = '\0';
  copystr(&ret, start);
  *cp = c;

  /* Check if the argument is a variable name */
  if (checkvar(ret)) {
	char *val;

	val = findvar(ret);
	copystr(&ret, val);
  }
  return(ret);
}



/*
 * Handle "$set" command
 */
static void doset __F((arg), char *arg)
{
  char *cp, *val;

  /* Only handle this command depending on $if state */
  if (curfile->iflevel >= 0 &&
      curfile->ifstate[curfile->iflevel] != IFSTATE_TRUE)
	return;

  /* Check that we have a variable name */
  if (!*arg) {
	doerror("missing variable name", NULL);
	return;
  }

  /* Find end of variable name */
  cp = findend(arg);

  /* Find the variable value string */
  val = findstart(cp);
  *cp = '\0';

  /* Check that the variable name contains only valid characters */
  if (!checkvar(arg)) {
	doerror("invalid variable name \"%s\"", arg);
	return;
  }

  /* Set variable */
  cp = doexpr(&val, '\0');
  setvar(arg, cp);
  if (cp != NULL)
	free(cp);
}



/*
 * Handle "$include" command
 */
static void doinclude __F((arg), char *arg)
{
  char *fname;

  /* Only handle this command depending on $if state */
  if (curfile->iflevel >= 0 &&
      curfile->ifstate[curfile->iflevel] != IFSTATE_TRUE)
	return;

  /* Determine include file name */
  fname = doexpr(&arg, '\0');
  if (*arg != '\0')
	doerror("junk following include file name", NULL);

  /* Check that we have an include file name */
  if (fname == NULL || !*fname) {
	if (fname != NULL)
		free(fname);
	doerror("missing include file name", NULL);
	return;
  }

  /* Find the include file */
  checkaccess(&fname, nbhomedir, ACCESS_FILE_READ);
  if (fname == NULL) {
	doerror("include file not found", NULL);
	return;
  }

  /* Now open the include file */
  openfile(fname);
  free(fname);
}



/*
 * Handle "exists" conditional command
 */
static void doexists __F((arg, invert), char *arg AND int invert)
{
  char *cp;

  /* Determine include file name */
  cp = doexpr(&arg, '\0');
  if (*arg != '\0') {
	if (cp != NULL)
		free(cp);
	doerror("junk following file name in \"exists\" command", NULL);
	return;
  }

  /* Check that we have a file name */
  if (cp == NULL || !*cp) {
	if (cp != NULL)
		free(cp);
	doerror("missing file name in \"exists\" command", NULL);
	return;
  }

  /* Check if the file exists */
  checkaccess(&cp, nbhomedir, ACCESS_FILE_EXIST);
  if (cp != NULL) {
	/* File exists */
	free(cp);
	setif(IFSTATE_TRUE, invert);
  } else {
	/* File does not exist */
	setif(IFSTATE_FALSE, invert);
  }
}



/*
 * Handle "defined" conditional command
 */
static void dodefined __F((arg, invert), char *arg AND int invert)
{
  char *cp;

  /* Check that we have a variable name */
  if (!*arg) {
	doerror("missing variable name in \"defined\" condition", NULL);
	return;
  }

  /* Find end of variable name */
  cp = findend(arg);
  *cp = '\0';

  /* Check that the variable name contains only valid characters */
  if (!checkvar(arg)) {
	doerror("invalid variable name \"%s\"", arg);
	return;
  }

  /* Check if the variable has been defined */
  setif((findvar(arg) == NULL) ? IFSTATE_FALSE : IFSTATE_TRUE, invert);
}



/*
 * Handle "equal" conditional command
 */
static void doequal __F((arg, invert), char *arg AND int invert)
{
  char *arg1, *arg2;

  /* Check that we have arguments at all */
  if (!*arg) {
	doerror("missing arguments to equal command", NULL);
	return;
  }

  /* Isolate first argument */
  arg1 = doexpr(&arg, ',');
  if (*arg != ',') {
	if (arg1 != NULL)
		free(arg1);
	doerror("missing comma in \"equal\" command", NULL);
	return;
  }

  /* Isolate second argument */
  arg = findstart(arg + 1);
  arg2 = doexpr(&arg, '\0');
  if (*arg != '\0') {
	if (arg1 != NULL)
		free(arg1);
	if (arg2 != NULL)
		free(arg2);
	doerror("junk following second argument in \"equal\" command", NULL);
	return;
  }

  /* Check if both strings are equal */
  if (arg1 == NULL && arg2 == NULL)
	setif(IFSTATE_TRUE, invert);
  else if (arg1 != NULL && arg2 != NULL)
	setif(!strcmp(arg1, arg2) ? IFSTATE_TRUE : IFSTATE_FALSE, invert);
  else
	setif(IFSTATE_FALSE, invert);

  if (arg1 != NULL)
	free(arg1);
  if (arg2 != NULL)
	free(arg2);
}



/*
 * Handle "$if" command
 */
static void doif __F((arg), char *arg)
{
  char *cp, *arg1;
  int invert = FALSE;

  /* Skip any checking if we are not currently active */
  if (curfile->iflevel >= 0 &&
      curfile->ifstate[curfile->iflevel] != IFSTATE_TRUE) {
	setif(IFSTATE_SKIP, FALSE);
	return;
  }

  /* Check if we have to invert the condition */
  if (!strncmp(arg, "not", 3) && (arg[3] == ' ' || arg[3] == '\t')) {
	invert = TRUE;
	arg = findstart(arg + 3);
  }

  /* Check that we have a condition */
  if (!*arg) {
	doerror("missing condition in \"if\" command", NULL);
	return;
  }

  /* Find end of condition string */
  cp = findend(arg);

  /* Find beginning of argument */
  arg1 = findstart(cp);
  *cp = '\0';

  /* Handle condition command */
  if (!strcmp(arg, "equal"))
	doequal(arg1, invert);
  else if (!strcmp(arg, "exists"))
	doexists(arg1, invert);
  else if (!strcmp(arg, "defined"))
	dodefined(arg1, invert);
  else
	doerror("unknown condition in \"if\" command", NULL);
}



/*
 * Handle "$else" command
 */
static void doelse __F((arg), char *arg)
{
  /* Check if we are within an $if level */
  if (curfile->iflevel < 0) {
	doerror("unmatched $else command", NULL);
	return;
  }

  /* Invert current level, this does not change IFSTATE_SKIP */
  if (curfile->ifstate[curfile->iflevel] == IFSTATE_TRUE)
	curfile->ifstate[curfile->iflevel] = IFSTATE_FALSE;
  else if (curfile->ifstate[curfile->iflevel] == IFSTATE_FALSE)
	curfile->ifstate[curfile->iflevel] = IFSTATE_TRUE;
}



/*
 * Handle "$endif" command
 */
static void doendif __F((arg), char *arg)
{
  /* Check if we are within an $if level */
  if (curfile->iflevel < 0) {
	doerror("unmatched $endif command", NULL);
	return;
  }

  /* Terminate current $if level */
  curfile->iflevel--;
}



/*
 * Process line beginning with a dollar sign
 */
static void dodollar __F((buf), char *buf)
{
  char *cp, *arg;

  /* Isolate the command string */
  cp = findend(buf);

  /* Find the argument string */
  arg = findstart(cp);
  *cp = '\0';

  /* Process the command */
  if (!strcmp(buf, "include"))
	doinclude(arg);
  else if (!strcmp(buf, "set"))
	doset(arg);
  else if (!strcmp(buf, "if"))
	doif(arg);
  else if (!strcmp(buf, "else"))
	doelse(arg);
  else if (!strcmp(buf, "endif"))
	doendif(arg);
  else
	doerror("unknown meta-command \"%s\"", buf);
}




/*
 *********************************************************************************
 *
 *		Routines to read a line from the input file
 *
 *********************************************************************************
 */


/*
 * Put a character at the end of the input buffer
 */
static void putbuf __F((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
}



/*
 * Substitute a variable
 */
static void substvar __F((name), char *name)
{
  char *cp;

  /* Check if we have a variable name at all */
  if (*name != '%')
	return;

  /* Find variable value */
  cp = findvar(name + 1);

  /* Set next write position to where the variable name was */
  curpos = name;

  /* Now copy the variable value into the output buffer */
  if (cp != NULL)
	while (*cp) {
		putbuf(*cp);
		cp++;
	}
}



/*
 * Read a line from the text file
 */
static char *readline __F((keepbuf), int keepbuf)
{
  int inquote = FALSE;
  int backslash = FALSE;
  int c = 0;
  char *cp;
  char *var = NULL;

  /* Check for EOF */
  if (curfile == NULL || curfile->fd == NULL)
	return(NULL);
  else if (feof(curfile->fd))
	c = EOF;

  /* The outer loop skips any empty lines */
  while (TRUE) {

	/* Check if we already have an input line */
	if (keepbuf) {
		keepbuf = FALSE;
		if (curpos != NULL && curpos > inbuf)
			goto procbuf;
	}
	curpos = NULL;

	/* Check for EOF on input file */
	if (c == EOF) {
		/* EOF on current include file */
		if (curfile->iflevel >= 0)
			doerror("unterminated $if command", NULL);
		if (curfile->next != NULL) {
			/* Close current include file */
			closefile();
		} else {
			/* At end of main file, terminate reading */
			delvars();
			return(NULL);
		}
	}

	/* Read one line */
	while ((c = fgetc(curfile->fd)) != EOF) {
		if (var != NULL) {
			/*
			 * Check if we are still reading a variable name.
			 * Such a name has to end with a percent sign.
			 */
			if (c == '%') {
				putbuf('\0');
				substvar(var);
				var = NULL;
				continue;
			}

			/*
			 * A variable name has to contain alpha characters
			 * only. Otherwise we don't treat the string as a
			 * variable name.
			 */
			if (((var == curpos) && isalpha(c)) ||
			    (isalnum(c) || c == '_')) {
				putbuf(c);
				continue;
			}
			var = NULL;
		}

		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 */
					curfile->curline++;
					break;
				default:
					putbuf(c);
					break;
			}
			backslash = FALSE;
			continue;
		}

		/*
		 * Check for end of line and special characters, and then
		 * put the character into the input buffer. Newline and
		 * comment characters get copied verbatim if we are within
		 * a quoted area.
		 */
		if (c == '#' && !inquote) {
			/*
			 * When encountering a comment sign, skip the rest of
			 * the line until newline
			 */
			while ((c = fgetc(curfile->fd)) != '\n' && c != EOF)
				;
			if (c == EOF)
				break;
			curfile->curline++;
			continue;
		} else if (c == '\n') {
			/*
			 * A newline gets copied into the output buffer if
			 * we are within quotes. Otherwise terminate reading.
			 */
			curfile->curline++;
			if (inquote)
				putbuf(c);
			else
				break;
		} else if (c == '"') {
			/*
			 * Start or terminate quoting area. The quote character
			 * itself gets copied into the output buffer.
			 */
			inquote = !inquote;
			putbuf(c);
		} else if (c == '%') {
			/* Start variable substitution */
			var = curpos;
			putbuf(c);
		} else if (c == '\\') {
			/* Start backslash processing */
			backslash = TRUE;
		} else
			putbuf(c);
	}
	putbuf('\0');

	/* Check for read error */
	if (c == EOF) {
		if (!feof(curfile->fd)) {
			nblib_parse_error = PARSESTATUS_READERR;
			return(NULL);
		} else if (inquote)
			doerror("unterminated string", NULL);
	}

	/* Process input line */
procbuf:

	/* 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++)
			;
		/* Handle meta-commands */
		if (*cp == '$')
			dodollar(cp + 1);
		else if (curfile->iflevel < 0 ||
		         curfile->ifstate[curfile->iflevel] == IFSTATE_TRUE)
			/* Return line */
			return(cp);
	}
  }
}



/*
 *********************************************************************************
 *
 *		Routines to delete a whole file memory image
 *
 *********************************************************************************
 */


/*
 * Delete all file locations
 */
static void dellocs __F((info), struct parseinfo *info)
{
  struct parseloc *p, *q;

  p = info->loclist;
  while (p != NULL) {
	q = p;
	p = p->next;
	if (q->fname != NULL)
		free(q);
  }
}



/*
 * Delete all parse meta-data items
 */
static void delparseitems __F((rec), struct parserec *rec)
{
  struct parseitem *p, *q;

  p = rec->parselist;
  while (p != NULL) {
	/*
	 * All pointers in the meta-data structure point to structures in
	 * some other list, so that we don't need to delete them here.
	 */
	q = p;
	p = p->next;
	free(q);
  }
}



/*
 * Delete all item data structures
 */
static void deldbitems __F((rec), struct parserec *rec)
{
  struct dbitem *p, *q;

  p = rec->datalist;
  while (p != NULL) {
	q = p;
	p = p->next;
	if (q->name != NULL)
		free(q->name);
	if (q->type == item_string && q->val.s != NULL)
		free(q->val.s);
	free(q);
  }
}



/*
 * Delete all file data represented by an info record
 */
static void delinfo __F((info), struct parseinfo *info)
{
  struct parserec *p, *q;

  /* Delete all records */
  p = info->reclist;
  while (p != NULL) {
	q = p;
	p = p->next;
	delparseitems(q);
	deldbitems(q);
	if (q->name != NULL)
		free(q->name);
  }

  /* Delete list of file locations and info record */
  dellocs(info);
  free(info);
}




/*
 *********************************************************************************
 *
 *		Routines to parse the input file
 *
 *********************************************************************************
 */


/*
 * Parse a string representing an item value
 */
static void parsevalue __F((buf, item), char *buf AND struct dbitem *item)
{
  char *cp;
  long l;

  /* Check if the item is empty */
  if (buf == NULL || !*buf) {
	item->type = item_none;
	return;
  }

  /* Check if this is a string item */
  if (*buf == '"') {
	size_t len;

	buf++;
	len = strlen(buf);
	if (buf[len - 1] == '"')
		buf[len - 1] = '\0';
	copystr(&(item->val.s), buf);
	item->type = item_string;
	return;
  }

  /* Check if this is a boolean TRUE value */
  if (!strcmp(buf, "TRUE") || !strcmp(buf, "true")) {
	item->val.b = TRUE;
	item->type = item_boolean;
	return;
  }

  /* Check if this is a boolean FALSE value */
  if (!strcmp(buf, "FALSE") || !strcmp(buf, "false")) {
	item->val.b = FALSE;
	item->type = item_boolean;
	return;
  }

  /* Check if this is a numeric value */
  errno = 0;
  l = strtol(buf, &cp, 0);
  if (!*cp && errno == 0) {
	item->val.i = l;
	item->type = item_integer;
	return;
  }

  /* Everything else is to be copied verbatim as a string value */
  copystr(&(item->val.s), buf);
  item->type = item_string;
}



/*
 * Read one section from database text file. Returns FALSE if record not
 * read.
 */
static struct parserec *readsect __F((info), struct parseinfo *info)
{
  char *cp, *sectname, *varname, *varend;
  struct parseitem *newpi, *lastpi;
  struct dbitem *newdi, *lastdi;
  struct parserec *newrec;

  /* Read section name */
  if ((cp = readline(TRUE)) == NULL)
	return(NULL);
  if (*cp != '[') {
	doerror("section name expected", NULL);
	while (*cp != '[') {
		if ((cp = readline(FALSE)) == NULL)
			return(NULL);
	}
  }

  /* Create new record structure */
  newrec = (struct parserec *)nbmalloc(sizeof(struct parserec));

  /* Determine name of section */
  sectname = cp + 1;
  if ((cp = strchr(sectname, ']')) == NULL)
	doerror("missing end-bracket following section name", NULL);
  else {
	*cp = '\0';
	if (*(cp + 1))
		doerror("junk following section name", NULL);
  }
  copystr(&(newrec->name), sectname);
  newrec->lineno = curfile->curline;
  newrec->loc = addloc(curfile->fname, info);
  newrec->itemnum = 0;
  newrec->datalist = NULL;
  newrec->datalast = NULL;
  newrec->parselist = NULL;
  newrec->parselast = NULL;

  /* Now read all items of current record, each line contains one item */
  lastdi = NULL;
  lastpi = NULL;
  while ((cp = readline(FALSE)) != NULL && *cp != '[') {

	/* Determine item name */
	for (varname = cp; *cp && *cp != ' ' && *cp != '\t' && *cp != '='; cp++)
		;
	for (varend = cp; *cp && (*cp == ' ' || *cp == '\t'); cp++)
		;
	if (*cp != '=') {
		doerror("missing equal sign", NULL);
		continue;
	}
	*varend = '\0';

	/* Find beginning of item value */
	for (cp++; *cp && (*cp == ' ' || *cp == '\t'); cp++)
		;

	/* Create new data item structure */
	newdi = (struct dbitem *)nbmalloc(sizeof(struct dbitem));
	copystr(&(newdi->name), varname);
	parsevalue(cp, newdi);
	if (lastdi == NULL)
		newrec->datalist = newdi;
	else
		lastdi->next = newdi;
	lastdi = newdi;

	/* Create new meta-data item structure */
	newpi = (struct parseitem *)nbmalloc(sizeof(struct parseitem));
	newpi->lineno = curfile->curline;
	newpi->loc = addloc(curfile->fname, info);
	newpi->data = newdi;
	if (lastpi == NULL)
		newrec->parselist = newpi;
	else
		lastpi->next = newpi;
	lastpi = newpi;

	/* Count new item */
	newrec->itemnum++;
  }
  newrec->datalast = lastdi;
  newrec->parselast = lastpi;
  return(newrec);
}




/*
 *********************************************************************************
 *
 *		Public routines
 *
 *********************************************************************************
 */


/*
 * Parse a whole text file and copy it into memory
 */
struct parseinfo *nblib_parse_file __F((fd, fname, doerr),
				FILE *fd AND
				const char *fname AND
				parseerr doerr)
{
  struct parseinfo *newinfo;
  struct parserec *currec, *lastrec;

  /* Print debugging info */
  prnlog(LOGLEVEL_DEBUG, "start reading file %s\n", fname);

  /* Create a new file list entry */
  curfile = (struct fdef *)nbmalloc(sizeof(struct fdef));
  copystr(&(curfile->fname), fname);
  curfile->curline = 0;
  curfile->fd = fd;
  curfile->iflevel = -1;
  curfile->next = NULL;

  /* Set global variables */
  nblib_parse_error = PARSESTATUS_SUCCESS;
  errorhandler = doerr;
  curpos = NULL;

  /* Create new info structure */
  newinfo = (struct parseinfo *)nbmalloc(sizeof(struct parseinfo));
  newinfo->reclist = NULL;
  newinfo->loclist = NULL;

  /* Read all records */
  lastrec = NULL;
  while (TRUE) {
	/* Read next record */
	if ((currec = readsect(newinfo)) != NULL) {
		if (lastrec == NULL)
			newinfo->reclist = currec;
		else
			lastrec->next = currec;
		lastrec = currec;
	} else {
		/* Terminate reading in case of error or EOF */
		break;
	}
  }

  /* In case of an error, delete all record data */
  if (nblib_parse_error != PARSESTATUS_SUCCESS) {
	delinfo(newinfo);
	newinfo = NULL;
  }

  /* Close all include files */
  while(curfile->next != NULL)
	closefile();

  /* Free the main file descriptor */
  if (curfile->fname != NULL)
	free(curfile->fname);
  free(curfile);

  /* Delete all variables */
  delvars();

  /* Print debugging info */
  prnlog(LOGLEVEL_DEBUG, "finished reading file %s\n", fname);

  /* Return new info structure */
  return(newinfo);
}



/*
 * Delete the whole parsed file contents
 */
void nblib_parse_free __F((info), struct parseinfo *info)
{
  if (info != NULL)
	delinfo(info);
}

