/*
 * db.c  -  Database interface routines
 *
 * 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: db.c,v 1.18 2007/01/06 18:31:38 gkminix Exp $
 */

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



/*
 * Local variables
 */
static struct dbhandle *curhdl = NULL;	/* current database handle */
static const char *errproc = NULL;	/* current procedure in case of error */
static int errconfig = -1;		/* database config in case of error */
static int noerror = FALSE;		/* don't print error messages */
static int inerror = FALSE;		/* TRUE if currently processing error */



/*
 * Global variables
 */
unsigned int dberrors;			/* number of errors in last operation */
unsigned int dbwarnings;		/* number of warnings in last op */




/*
 *********************************************************************************
 *
 *		Structures for accessing different database types
 *
 *********************************************************************************
 */


/*
 * Externals
 */
extern struct dbprocs db_textprocs;
#ifdef HAVE_BDB
extern struct dbprocs db_bdbprocs;
#endif
#ifdef HAVE_ODBC
extern struct dbprocs db_odbcprocs;
#endif



/*
 * List of supported database types
 */
static struct {
	enum dbtype       type;
	struct dbprocs   *procs;
} dblist[] = {
  { dbtype_text,	&db_textprocs },
#ifdef HAVE_BDB
  { dbtype_bdb,		&db_bdbprocs },
#endif
#ifdef HAVE_ODBC
  { dbtype_sql,		&db_odbcprocs },
#endif
  { dbtype_none,	NULL }
};




/*
 *********************************************************************************
 *
 *		Global routines used internally by the netboot library
 *
 *********************************************************************************
 */


/*
 * Print an error message. This routine is intended to be called by the
 * low-level database modules.
 */
void nblib_db_error __F((msg, class), const char *msg AND dberrclass class)
{
  char *classmsg, *dbname = NULL;

  /* Increase error count */
  if (class == DBERR_CLASS_ERROR || class == DBERR_CLASS_FATAL)
	dberrors++;
  if (class == DBERR_CLASS_WARNING)
	dbwarnings++;

  /* Check that we have handle and should print error messages */
  if (curhdl == NULL || noerror || inerror)
	return;

  /* Set the error-processing flag */
  inerror = TRUE;

  /* Determine name of database */
  if (errconfig != -1) {
	dbname = curhdl->configs[errconfig]->name;
	if (dbname == NULL &&
	    curhdl->configs[errconfig]->type == dbtype_text) {
		/* Special case for text database files */
		dbname = curhdl->configs[errconfig]->config.text.fname;
	}
  }
  if (dbname == NULL)
	dbname = "unknown database";

  /* Check if we have a user defined error handler */
  if (curhdl->errhandler != NULL) {
	char *buf;

	if (msg != NULL)
		(curhdl->errhandler)(dbname, msg, class);
	else if (nberrmsg != NULL) {
		buf = (char *)nbmalloc(strlen(nberrmsg) + 20);
		sprintf(buf, "<%d>%s", nberrnum, nberrmsg);
		(curhdl->errhandler)(dbname, buf, class);
		free(buf);
	} else {
		buf = (char *)nbmalloc(20);
		sprintf(buf, "<%d>", nberrnum);
		(curhdl->errhandler)(dbname, buf, class);
		free(buf);
	}
	inerror = FALSE;
	return;
  }

  /* Print error message */
  switch (class) {
	case DBERR_CLASS_WARNING:
		classmsg = "Warning";
		break;
	case DBERR_CLASS_ERROR:
		classmsg = "Error";
		break;
	case DBERR_CLASS_FATAL:
		classmsg = "Fatal";
		break;
	default:
		/* We don't print info messages */
		return;
  }
  if (msg != NULL || nberrmsg != NULL) {
	  prnerr("%s: [%s] %s", classmsg, dbname,
					(msg == NULL ? nberrmsg : msg));
  }

  /* Terminate in case of fatal error */
  if (class == DBERR_CLASS_FATAL) {
	if (msg == NULL && nberrnum > 0)
		nbexit(nberrnum);
	nbexit(EXIT_DB);
  }

  /* Reset error-processing flag */
  inerror = FALSE;
}





/*
 *********************************************************************************
 *
 *		Support routines
 *
 *********************************************************************************
 */


/*
 * Set the current database config number for error messages and check
 * if we can call a database procedure
 */
static inline void checkproc __F((dbnum), int dbnum)
{
  errconfig = dbnum;
  if (curhdl->procs[dbnum] == NULL) {
	if (errproc != NULL)
		prnerr("missing database function '%s'", errproc);
	else
		prnerr("missing unknown database function");
	nbexit(EXIT_INTERNAL);
  }
}



/*
 * Check current database position
 */
static int checkpos __F((checkread), int checkread)
{
  if (curhdl->dbnum == 0 ||
      (checkread &&
       (curhdl->dbread == -1 || curhdl->dbids[curhdl->dbread] == DBID_NULL))) {
	nblib_db_error("invalid database position", DBERR_CLASS_ERROR);
	curhdl->dbread = -1;
	return(FALSE);
  }
  return(TRUE);
}



/*
 * Find a handle structure
 */
static int findhandle __F((handle, procname), DBHDL handle AND const char *procname)
{
  /* Reset number of errors */
  dberrors = 0;
  dbwarnings = 0;
  errconfig = -1;
  errproc = procname;

  /* Check that the handle is correct - should never happen */
  if (!checkdb(handle)) {
	nblib_db_error("invalid database handle", DBERR_CLASS_ERROR);
	return(FALSE);
  }

  /* Determine handle pointer */
  curhdl = (struct dbhandle *)handle;
  return(TRUE);
}



/*
 * Terminate processing of a database in case of error
 */
static void terminate __F((handle), DBHDL handle)
{
  struct dbhandle *hdl = (struct dbhandle *)handle;
  int i, old_noerror = noerror;

  /* Delete last found record name */
  if (hdl->lastfind != NULL) {
	free(hdl->lastfind);
	hdl->lastfind = NULL;
  }

  /*
   * Close all databases in current handle. We do not allow error
   * processing, because this could also lead to recursions.
   */
  noerror = TRUE;
  for (i = 0; i < hdl->dbnum; i++) {
	if (hdl->dbids[i] != DBID_NULL) {
		signal_stop();
		if (hdl->procs[i] != NULL)
			(hdl->procs[i]->closeptr)(hdl->dbids[i]);
		hdl->dbids[i] = DBID_NULL;
		signal_resume();
	}
	hdl->flags[i] &= DBFLAGS_GLOBAL;
  }
  noerror = old_noerror;
}



/*
 * Error routine for section parser
 */
static void errparse __F((msg, recname, item),
				const char *msg AND
				const char *recname AND
				const struct dbitem *item)
{
  char *buf, *cp;

  /* Let the database module generate the error location */
  cp = (curhdl->procs[errconfig]->errmsgptr)(curhdl->dbids[errconfig],
							recname, item);
  if (cp == NULL) {
	cp = (char *)nbmalloc(strlen(recname) + strlen(item->name) + 16);
	sprintf(cp, "Record %s, Item %s", recname, item->name);
  }

  /* Generate error message */
  buf = (char *)nbmalloc(strlen(cp) + strlen(msg) + 4);
  sprintf(buf, "%s: %s", cp, msg);
  nblib_db_error(buf, DBERR_CLASS_ERROR);
  free(buf);
  free(cp);
}



/*
 * Convert a string into a parameter value
 */
static void convert __F((arg, item), char *arg AND struct dbitem *item)
{
  char *end;
  long l;

  /* Check if we have an argument at all */
  if (arg == NULL) {
	item->type = item_none;
	return;
  }

  /* Check for boolean value */
  if (!strcmp(arg, "true") || !strcmp(arg, "TRUE")) {
	item->type = item_boolean;
	item->val.b = TRUE;
	return;
  }
  if (!strcmp(arg, "false") || !strcmp(arg, "FALSE")) {
	item->type = item_boolean;
	item->val.b = FALSE;
	return;
  }

  /* Check for integer value */
  errno = 0;
  l = strtol(arg, &end, 0);
  if (!*end && errno == 0) {
	item->type = item_integer;
	item->val.i = l;
	return;
  }

  /* Everything else is a string. We don't need to use copystr here. */
  item->type = item_string;
  item->val.s = arg;
}



/*
 * Remove the netboot data directory name from a file name
 */
static char *cleanfile __F((src), const char *src)
{
  char *cp;
  size_t len1, len2;

  /* Check that we have a file name at all */
  if (src == NULL || !*src)
	return(NULL);

  /* Do not convert multiple file names, only the first one */
  if ((cp = strchr(src, ':')) != NULL)
	len1 = (size_t)(cp - src);
  else
	len1 = strlen(src);

  /* Check if the file name contains any of the netboot data directories */
  if (
      /* Check for nblibdir */
      (nblibdir == NULL || len1 <= (len2 = strlen(nblibdir)) ||
       src[len2] != '/' || strncmp(src, nblibdir, len2) != 0) &&

      /* Check for nbdatadir */
      (nbdatadir == NULL || len1 <= (len2 = strlen(nbdatadir)) ||
       src[len2] != '/' || strncmp(src, nbdatadir, len2) != 0)) {

	/* Netboot data directory name not found */
	cp = (char *)nbmalloc(len1 + 1);
	strncpy(cp, src, len1);
	cp[len1] = '\0';
	return(cp);
  }

  /* Replace the data directory name with a slash */
  len1 -= len2;
  cp = (char *)nbmalloc(len1 + 2);
  cp[0] = '/';
  strncpy(&(cp[1]), &(src[len2]), len1);
  cp[len1 + 1] = '\0';
  return(cp);
}



/*
 * Generate an item list out of a parameter list
 */
static struct dbitem *encode __F((params), const struct paramdef *params)
{
  struct dbitem *items, *lastitem, *curitem;
  struct enumdef *ep;
  char *arg;
  int i;

  items = lastitem = NULL;
  for (i = 0; params[i].name != NULL; i++) {
	curitem = (struct dbitem *)nbmalloc(sizeof(struct dbitem));
	switch (params[i].type) {
		case par_string:
			curitem->type = item_string;
			copystr(&(curitem->val.s), *(params[i].valptr.strptr));
			break;
		case par_file:
			curitem->type = item_string;
			curitem->val.s = cleanfile(*(params[i].valptr.fnamptr));
			break;
		case par_int:
			curitem->type = item_integer;
			curitem->val.i = *(params[i].valptr.intptr);
			break;
		case par_long:
			curitem->type = item_integer;
			curitem->val.i = *(params[i].valptr.longptr);
			break;
		case par_bool:
			curitem->type = item_boolean;
			curitem->val.b = *(params[i].valptr.boolptr);
			break;
		case par_enum:
			curitem->type = item_string;
			for (ep = params[i].enumlist; ep->enumstr != NULL; ep++)
				if (ep->val == *(params[i].valptr.enumptr))
					break;
			if (ep->enumstr == NULL || ep->enumstr[0] == '\0')
				copystr(&(curitem->val.s), NULL);
			else
				copystr(&(curitem->val.s), ep->enumstr);
			break;
		case par_proc:
			arg = NULL;
			(void)(params[i].valptr.procptr)(params[i].name,
								&arg, TRUE);
			convert(arg, curitem);
			break;
		case par_null:
		default:
			free(curitem);
			continue;
	}
	copystr(&(curitem->name), params[i].name);
	if (items == NULL)
		items = curitem;
	else
		lastitem->next = curitem;
	curitem->next = NULL;
	lastitem = curitem;
  }
  return(items);
}




/*
 *********************************************************************************
 *
 *		Global database interface routines
 *
 *********************************************************************************
 */


/*
 * Release all locks for a database. Whether this succeeds depends on
 * the underlying database engine. The locks get re-estabilshed
 * as soon as a new cursor position is selected. Using this function
 * it is possible to improve concurrent access performance.
 */
void releasedb __F((handle), DBHDL handle)
{
  int i;

  /* Find database handle and reset error count */
  if (!findhandle(handle, "releasedb"))
	return;

  /* Scan through all databases and release them individually */
  for (i = 0; i < curhdl->dbnum; i++) {
	if (curhdl->dbids[i] != DBID_NULL) {
		checkproc(i);
		signal_stop();
		(curhdl->procs[i]->releaseptr)(curhdl->dbids[i]);
		signal_resume();
	}
  }

  /* Reset last search position */
  curhdl->dbread = -1;
  curhdl->dbmark = -1;
  if (curhdl->lastfind != NULL) {
	free(curhdl->lastfind);
	curhdl->lastfind = NULL;
  }

  /* Reset current handle */
  curhdl = NULL;
}



/*
 * Close all database connections for a handle
 */
void closedb __F((handle), DBHDL handle)
{
  int i;

  /* Find database handle and reset error count */
  if (!findhandle(handle, "closedb"))
	return;

  /* Scan through all databases and close them individually */
  for (i = 0; i < curhdl->dbnum; i++) {
	if (curhdl->dbids[i] != DBID_NULL) {
		checkproc(i);
		signal_stop();
		(curhdl->procs[i]->closeptr)(curhdl->dbids[i]);
		curhdl->dbids[i] = DBID_NULL;
		signal_resume();
	}
	curhdl->flags[i] &= DBFLAGS_GLOBAL;
  }

  /* Delete last found record name */
  if (curhdl->lastfind != NULL) {
	free(curhdl->lastfind);
	curhdl->lastfind = NULL;
  }

  /* Reset current handle */
  curhdl = NULL;
}



/*
 * Open a new database connection
 */
int opendb __F((handle, readonly), DBHDL handle AND int readonly)
{
  int i, j, flags, opencount;

  /* Find database handle and reset error count */
  if (!findhandle(handle, "opendb"))
	return(0);

  /* Scan through all databases and open them individually */
  opencount = 0;
  for (i = 0; i < curhdl->dbnum; i++) {
	if (curhdl->dbids[i] != DBID_NULL || curhdl->configs[i] == NULL)
		continue;
	flags = curhdl->flags[i] & DBFLAGS_GLOBAL;
	if (readonly) {
		flags |= DBFLAGS_RO;
		flags &= ~DBFLAGS_ADD;
		curhdl->flags[i] |= DBFLAGS_FORCERO;
	}
	curhdl->procs[i] = NULL;
	for (j = 0; dblist[j].type != dbtype_none; j++)
		if (curhdl->configs[i]->type == dblist[j].type)
			break;
	if (dblist[j].type != dbtype_none)
		curhdl->procs[i] = dblist[j].procs;
	errconfig = i;
	if (curhdl->procs[i] == NULL) {
		nblib_db_error("database type unsupported",
					((flags & DBFLAGS_OPT) == 0 ?
							DBERR_CLASS_ERROR :
							DBERR_CLASS_WARNING));
		curhdl->dbids[i] = DBID_NULL;
	} else {
		signal_stop();
		curhdl->dbids[i] =
			(curhdl->procs[i]->openptr)(curhdl->configs[i], flags);
		signal_resume();
	}
	if (curhdl->dbids[i] != DBID_NULL)
		opencount++;
  }

  /* Set termination pointer */
  curhdl->termhandler = (opencount > 0 ? &terminate : NULL);

  /* Reset last search position */
  curhdl->dbread = -1;
  curhdl->dbmark = -1;
  if (curhdl->lastfind != NULL) {
	free(curhdl->lastfind);
	curhdl->lastfind = NULL;
  }

  /* Reset current handle and return number of open databases */
  curhdl = NULL;
  return(opencount);
}



/*
 * Get status of database at current read position. This routine never
 * asserts an error condition.
 */
int getstatedb __F((handle, recname), DBHDL handle AND char **recname)
{
  int i, ret = 0;
  char *name;

  /* Ignore any errors */
  noerror = TRUE;

  /* Find database handle and reset error count */
  if (!findhandle(handle, "getstatedb"))
	return(0);

  /* Determine return flags and current record name */
  if (curhdl->dbnum > 0 && curhdl->dbread >= 0) {
	i = curhdl->dbread;
	checkproc(i);
	signal_stop();
	name = (curhdl->procs[i]->curposptr)(curhdl->dbids[i]);
	signal_resume();
	if (dberrors == 0) {
		ret |= DBSTATUS_VALID;
		if ((curhdl->flags[i] & (DBFLAGS_RO | DBFLAGS_FORCERO)) != 0)
			ret |= DBSTATUS_RO;
		if ((curhdl->flags[i] & DBFLAGS_ADD) != 0)
			ret |= DBSTATUS_ADD;
		if (i == (curhdl->dbnum - 1))
			ret |= DBSTATUS_LAST;
		if (name != NULL)
			ret |= DBSTATUS_POS;
		if (recname != NULL) {
			*recname = name;
			name = NULL;
		}
	}
	if (name != NULL)
		free(name);
  }

  /* Reset current handle */
  noerror = FALSE;
  curhdl = NULL;
  return(ret);
}



/*
 * Proceed current read location to first record of next database
 */
int nextdb __F((handle), DBHDL handle)
{
  int ret = FALSE;
  int i;

  /* Find database handle and reset error count */
  if (!findhandle(handle, "nextdb"))
	return(FALSE);

  /* Check if there is at least one database and a valid read position */
  if (!checkpos(TRUE))
	goto nextdb_end;

  /* Advance read pointer to next database */
  i = curhdl->dbread;
  while (!ret) {
	i++;
	while (i < curhdl->dbnum && curhdl->dbids[i] == DBID_NULL)
		i++;
	if (i >= curhdl->dbnum)
		break;
	curhdl->dbread = i;
	checkproc(i);
	signal_stop();
	ret = (curhdl->procs[i]->firstptr)(curhdl->dbids[i]);
	signal_resume();
	if (dberrors > 0)
		break;
  }
  if (!ret)
	curhdl->dbread = -1;

  /* Reset current handle */
nextdb_end:
  curhdl = NULL;
  return(ret);
}



/*
 * Proceed current read location to last record of previous database
 */
int prevdb __F((handle), DBHDL handle)
{
  int ret = FALSE;
  int i;

  /* Find database handle and reset error count */
  if (!findhandle(handle, "prevdb"))
	return(FALSE);

  /* Check if there is at least one database and a valid read position */
  if (!checkpos(TRUE))
	goto prevdb_end;

  /* Advance read pointer across database boundaries */
  i = curhdl->dbnum;
  while (!ret) {
	i--;
	while (i >= 0 && curhdl->dbids[i] == DBID_NULL)
		i--;
	if (i < 0)
		break;
	curhdl->dbread = i;
	checkproc(i);
	signal_stop();
	ret = (curhdl->procs[i]->lastptr)(curhdl->dbids[i]);
	signal_resume();
	if (dberrors > 0)
		break;
  }
  if (!ret)
	curhdl->dbread = -1;

  /* Reset current handle */
prevdb_end:
  curhdl = NULL;
  return(ret);
}



/*
 * Internal routine to find the next occurrence of a record
 */
static int internal_next __F((first), int first)
{
  int i, ret = FALSE;

  /* Check if we have something to search at all */
  if (!checkpos(TRUE))
	goto find_end;

  /* Check that we have something to find */
  if (curhdl->lastfind == NULL)
	goto find_end;

  /* Scan all databases and find next occurrence */
  i = curhdl->dbread - 1;
  while (!ret) {
	i++;
	while (i < curhdl->dbnum && curhdl->dbids[i] == DBID_NULL)
		i++;
	if (i >= curhdl->dbnum)
		break;
	curhdl->dbread = i;
	checkproc(i);
	signal_stop();
	ret = (curhdl->procs[i]->findptr)(curhdl->dbids[i],
						curhdl->lastfind, first);
	signal_resume();
	if (dberrors > 0)
		break;
	first = TRUE;
  }

  /* Check for errors */
find_end:
  if (!ret) {
	if (curhdl->lastfind != NULL) {
		free(curhdl->lastfind);
		curhdl->lastfind = NULL;
	}
  }
  curhdl = NULL;
  return(ret);
}



/*
 * Find the next occurrence of a record
 */
int findnext __F((handle), DBHDL handle)
{
  /* Find database handle and reset error count */
  if (!findhandle(handle, "findnext"))
	return(FALSE);

  /* Find next record */
  return(internal_next(FALSE));
}



/*
 * Find the first occurrence of a record
 */
int findfirst __F((handle, name), DBHDL handle AND const char *name)
{
  int i;

  /* Find database handle and reset error count */
  if (!findhandle(handle, "findfirst"))
	return(FALSE);

  /* Reset read position and search for record */
  i = 0;
  while (i < curhdl->dbnum && curhdl->dbids[i] == DBID_NULL)
	i++;
  if (i >= curhdl->dbnum) {
	curhdl->dbread = -1;
	curhdl = NULL;
	return(FALSE);
  }
  curhdl->dbread = i;
  copystr(&(curhdl->lastfind), name);
  return(internal_next(TRUE));
}



/*
 * Proceed current read location to next record
 */
int nextrec __F((handle), DBHDL handle)
{
  int ret = FALSE;
  int i;

  /* Find database handle and reset error count */
  if (!findhandle(handle, "nextrec"))
	return(FALSE);

  /* Check if there is at least one database and a valid read position */
  if (!checkpos(TRUE))
	goto nextrec_end;

  /* Advance read pointer across database boundaries */
  i = curhdl->dbread - 1;
  while (!ret) {
	i++;
	while (i < curhdl->dbnum && curhdl->dbids[i] == DBID_NULL)
		i++;
	if (i >= curhdl->dbnum)
		break;
	curhdl->dbread = i;
	checkproc(i);
	signal_stop();
	ret = (curhdl->procs[i]->nextptr)(curhdl->dbids[i]);
	signal_resume();
	if (dberrors > 0)
		break;
  }

  /* Reset current handle */
nextrec_end:
  curhdl = NULL;
  return(ret);
}



/*
 * Proceed current read location to previous record
 */
int prevrec __F((handle), DBHDL handle)
{
  int ret = FALSE;
  int i;

  /* Find database handle and reset error count */
  if (!findhandle(handle, "prevrec"))
	return(FALSE);

  /* Check if there is at least one database and a valid read position */
  if (!checkpos(TRUE))
	goto prevrec_end;

  /* Advance read pointer across database boundaries */
  i = curhdl->dbread + 1;
  while (!ret) {
	i--;
	while (i >= 0 && curhdl->dbids[i] == DBID_NULL)
		i--;
	if (i < 0)
		break;
	curhdl->dbread = i;
	checkproc(i);
	signal_stop();
	ret = (curhdl->procs[i]->prevptr)(curhdl->dbids[i]);
	signal_resume();
	if (dberrors > 0)
		break;
  }

  /* Reset current handle */
prevrec_end:
  curhdl = NULL;
  return(ret);
}



/*
 * Proceed current read location to first record
 */
int firstrec __F((handle), DBHDL handle)
{
  int ret = FALSE;
  int i;

  /* Find database handle and reset error count */
  if (!findhandle(handle, "firstrec"))
	return(FALSE);

  /* Check if there is at least one database */
  if (!checkpos(FALSE))
	goto firstrec_end;

  /* Advance read pointer across database boundaries */
  i = -1;
  while (!ret) {
	i++;
	while (i < curhdl->dbnum && curhdl->dbids[i] == DBID_NULL)
		i++;
	if (i >= curhdl->dbnum)
		break;
	curhdl->dbread = i;
	checkproc(i);
	signal_stop();
	ret = (curhdl->procs[i]->firstptr)(curhdl->dbids[i]);
	signal_resume();
	if (dberrors > 0)
		break;
  }

  /* Reset current handle */
firstrec_end:
  curhdl = NULL;
  return(ret);
}



/*
 * Proceed current read location to last record
 */
int lastrec __F((handle), DBHDL handle)
{
  int ret = FALSE;
  int i;

  /* Find database handle and reset error count */
  if (!findhandle(handle, "lastrec"))
	return(FALSE);

  /* Check if there is at least one database */
  if (!checkpos(FALSE))
	goto lastrec_end;

  /* Advance read pointer across database boundaries */
  i = curhdl->dbnum + 1;
  while (!ret) {
	i--;
	while (i >= 0 && curhdl->dbids[i] == DBID_NULL)
		i--;
	if (i < 0)
		break;
	curhdl->dbread = i;
	checkproc(i);
	signal_stop();
	ret = (curhdl->procs[i]->lastptr)(curhdl->dbids[i]);
	signal_resume();
	if (dberrors > 0)
		break;
  }

  /* Reset current handle */
lastrec_end:
  curhdl = NULL;
  return(ret);
}



/*
 * Mark a record position
 */
int markrec __F((handle, jump), DBHDL handle AND int jump)
{
  int i, ret = FALSE;

  /* Find database handle and reset error count */
  if (!findhandle(handle, "markrec"))
	return(FALSE);

  /* Release any old bookmark, optionally jumping to it first */
  if (curhdl->dbmark != -1) {
	i = curhdl->dbmark;
	checkproc(i);
	signal_stop();
	ret = (curhdl->procs[i]->markptr)(curhdl->dbids[i],
					(jump ? BOOKMARK_JUMP : BOOKMARK_RELEASE));
	signal_resume();
	curhdl->dbmark = -1;
	if (jump && ret)
		curhdl->dbread = i;
  }

  /* Eventually setup a new mark */
  if (!jump && dberrors == 0) {
	if (!checkpos(TRUE))
		goto markrec_end;
	i = curhdl->dbread;
	checkproc(i);
	signal_stop();
	ret = (curhdl->procs[i]->markptr)(curhdl->dbids[i], BOOKMARK_SET);
	signal_resume();
	curhdl->dbmark = i;
  }

  /* Reset current handle */
markrec_end:
  curhdl = NULL;
  return(ret);
}



/*
 * Read a record from a database
 */
int readrec __F((handle, sects), DBHDL handle AND struct sectdef *sects)
{
  struct dbitem *items = NULL;
  char *recname = NULL;
  int i, ret = 0;

  /* Find database handle and reset error count */
  if (!findhandle(handle, "readrec"))
	return(0);

  /* Check if we have something to read at all */
  if (!checkpos(TRUE))
	goto readrec_end;

  /* Read current record */
  i = curhdl->dbread;
  checkproc(i);
  signal_stop();
  items = (curhdl->procs[i]->readptr)(curhdl->dbids[i], &recname);
  signal_resume();
  if (dberrors > 0 || items == NULL || recname == NULL) {
	if (dberrors == 0)
		nblib_db_error("error reading database record",
							DBERR_CLASS_ERROR);
	curhdl->dbread = -1;
	goto readrec_end;
  }

  /* Process the items which we read */
  ret = nblib_parse_sect(recname, items, sects, &errparse);

  /* Reset current handle */
readrec_end:
  if (recname != NULL)
	free(recname);
  curhdl = NULL;
  return(ret);
}



/*
 * Write a record into a database
 */
void writerec __F((handle, sects), DBHDL handle AND struct sectdef *sects)
{
  struct sectdef *cursect;
  struct dbitem *items;
  char *sectname, *recname = NULL;
  int i, len;

  /* Find database handle and reset error count */
  if (!findhandle(handle, "writerec"))
	return;

  /* Check if we have something to write at all */
  if (!checkpos(TRUE))
	goto writerec_end;

  /* Check if database is read-only */
  i = curhdl->dbread;
  if ((curhdl->flags[i] & (DBFLAGS_RO | DBFLAGS_FORCERO)) != 0) {
	nblib_db_error("writing into read-only database", DBERR_CLASS_ERROR);
	goto writerec_end;
  }

  /* Determine current record name */
  checkproc(i);
  signal_stop();
  recname = (curhdl->procs[i]->curposptr)(curhdl->dbids[i]);
  signal_resume();
  if (dberrors > 0 || recname == NULL) {
	if (dberrors == 0)
		nblib_db_error("error reading current database position",
							DBERR_CLASS_ERROR);
	curhdl->dbread = -1;
	goto writerec_end;
  }

  /* Find section which fits the current record name */
  for (cursect = sects; cursect->name != NULL; cursect++) {
	sectname = cursect->name;
	len = strlen(sectname);
	if (len > 2 && sectname[len - 2] == ':' &&
	               sectname[len - 1] == '*' &&
	               !strncmp(sectname, recname, len - 1)) {
		if (recname[len - 1] != '\0' &&
		    strchr(&(recname[len]), ':') == NULL)
			break;
	} else if (!strcmp(sectname, recname))
		break;
  }
  if (cursect->name == NULL) {
	nblib_db_error("no section found for writing", DBERR_CLASS_ERROR);
	goto writerec_end;
  }

  /* Create new item list */
  items = encode(cursect->params);
  if (items == NULL) {
	nblib_db_error("no items in record", DBERR_CLASS_ERROR);
	goto writerec_end;
  }

  /* Write record */
  checkproc(i);
  signal_stop();
  (curhdl->procs[i]->writeptr)(curhdl->dbids[i], items);
  signal_resume();

  /* Reset current handle */
writerec_end:
  if (recname != NULL)
	free(recname);
  curhdl = NULL;
}



/*
 * Add a new record into a database
 */
void addrec __F((handle, sects, sectindex),
				DBHDL handle AND
				struct sectdef *sects AND
				int sectindex)
{
  struct sectdef *cursect = &(sects[sectindex]);
  struct dbitem *items;
  char *cp;
  int i;

  /* Find database handle and reset error count */
  if (!findhandle(handle, "addrec"))
	return;

  /* Check if we have something to write at all */
  if (!checkpos(TRUE))
	goto addrec_end;

  /* Check for correct section name */
  cp = (cursect->name == NULL ? NULL : strchr(cursect->name, ':'));
  if (cp == NULL || cp[1] == '*' || cp[1] == '\0') {
	nblib_db_error("invalid record name for adding to database",
							DBERR_CLASS_ERROR);
	goto addrec_end;
  }

  /* Check if database allows adding records */
  i = curhdl->dbread;
  if ((curhdl->flags[i] & (DBFLAGS_RO | DBFLAGS_FORCERO | DBFLAGS_ADD)) !=
								DBFLAGS_ADD) {
	nblib_db_error("not allowed to add into database", DBERR_CLASS_ERROR);
	goto addrec_end;
  }

  /* Create new item list */
  items = encode(cursect->params);
  if (items == NULL) {
	nblib_db_error("no items in record", DBERR_CLASS_ERROR);
	goto addrec_end;
  }

  /* Add new record */
  checkproc(i);
  signal_stop();
  (curhdl->procs[i]->addptr)(curhdl->dbids[i], cursect->name, items);
  signal_resume();

  /* Reset current handle */
addrec_end:
  curhdl = NULL;
}



/*
 * Delete a record from a database
 */
void delrec __F((handle), DBHDL handle)
{
  int i;

  /* Find database handle and reset error count */
  if (!findhandle(handle, "delrec"))
	return;

  /* Check if we have something to delete at all */
  if (!checkpos(TRUE))
	goto delrec_end;

  /* Check if database allows writing */
  i = curhdl->dbread;
  if ((curhdl->flags[i] & (DBFLAGS_RO | DBFLAGS_FORCERO)) != 0) {
	nblib_db_error("deleting in read-only database", DBERR_CLASS_ERROR);
	goto delrec_end;
  }

  /* Delete the current record */
  checkproc(i);
  signal_stop();
  (curhdl->procs[i]->delptr)(curhdl->dbids[i]);
  signal_resume();

  /* Reset current handle */
delrec_end:
  curhdl = NULL;
}

