/*
 * dbbdb.c  -  Berkeley DB database 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: dbbdb.c,v 1.18 2007/01/06 18:31:38 gkminix Exp $
 */

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

#ifdef HAVE_BDB

#include DB_HEADER
#include "privlib.h"
#include "privdb.h"



/*
 * Check that configure detected Berkeley DB correctly
 */
#if !defined(DB_VERSION_MAJOR) || DB_VERSION_MAJOR < 4
# error Invalid version of Berkeley DB
#endif


/*
 * Default timeout for all databases
 */
#define DEFAULT_TIMEOUT	10



/*
 * Flag which indicates if the current cursor position is valid
 */
#define CURPOS_BOF	0		/* beginning of file */
#define CURPOS_EOF	1		/* end of file */
#define CURPOS_VALID	2		/* valid cursor position */



/*
 * Types of database items in each record
 */
#define DBTYPE_NONE	0
#define DBTYPE_STRING	1
#define DBTYPE_INTEGER	2
#define DBTYPE_BOOLEAN	3



/*
 * Structure holding information about an open database
 */
struct dbbdb {
	int               flags;	/* open flags */
	int               curpos;	/* current cursor status */
	DB               *db;		/* the database structure */
	DBC              *cursor;	/* current database cursor */
	DBC              *mark;		/* marked cursor */
	char             *currec;	/* current record name */
	struct dbitem    *curitems;	/* current item list */
	struct dbconfig  *config;	/* database configuration */
};



/*
 * Local variables
 */
static DB_ENV *dbenv = NULL;		/* database environment */
static int opencount = 0;		/* count of open handles */




/*
 * Delete all current record information
 */
static void cleardb __F((db, itemsonly), struct dbbdb *db AND int itemsonly)
{
  struct dbitem *ip;

  /* Clear the current record name */
  if (db->currec != NULL && !itemsonly) {
	free(db->currec);
	db->currec = NULL;
  }

  /* Clear the current item list */
  while (db->curitems != NULL) {
	ip = db->curitems;
	db->curitems = ip->next;
	if (ip->name != NULL)
		free(ip->name);
	if (ip->type == item_string && ip->val.s != NULL)
		free(ip->val.s);
	free(ip);
  }
}



/*
 * Decode a database record
 *
 * The data item list created with this routine remains in the ownership
 * of this module. If the application reads the current record and gets
 * this data item list, it should never modify it.
 */
static void decode __F((db, key, data),
				struct dbbdb *db AND
				DBT *key AND
				DBT *data)
{
  struct dbitem *ip;
  char *buf;
  size_t rsize, namelen, datalen;
  int itemtype, datavalid;

  /* First clear all old data */
  cleardb(db, FALSE);

  /* Copy the record name into the database handle */
  assert(key->data != NULL && key->size > 0);
  db->currec = (char *)nbmalloc(key->size);
  strncpy(db->currec, (char *)(key->data), key->size - 1);
  db->currec[key->size - 1] = '\0';

  /* Now decode the record data */
  assert(data->data != NULL && data->size > 0);
  buf = (char *)(data->data);
  rsize = (size_t)(data->size);
  while (rsize > 5) {
	/* Determine the item type and sizes */
	itemtype = (int)(buf[0]);
	namelen = (size_t)((int)(buf[1]) + ((int)(buf[2]) * 256));
	datalen = (size_t)((int)(buf[3]) + ((int)(buf[4]) * 256));
	if (rsize < (namelen + datalen + 5))
		break;
	rsize -= namelen + datalen + 5;
	buf += 5;

	/* Check for correct item name */
	if (namelen == 0 || *buf == '\0') {
		nblib_db_error("invalid item name", DBERR_CLASS_WARNING);
		buf += namelen + datalen;
		continue;
	}

	/* Create a new item structure */
	ip = (struct dbitem *)nbmalloc(sizeof(struct dbitem));
	ip->next = db->curitems;
	db->curitems = ip;

	/* Decode the item type */
	switch (itemtype) {
		case DBTYPE_NONE:
				ip->type = item_none;
				break;
		case DBTYPE_STRING:
				ip->type = item_string;
				break;
		case DBTYPE_INTEGER:
				ip->type = item_integer;
				break;
		case DBTYPE_BOOLEAN:
				ip->type = item_boolean;
				break;
		default:
				nblib_db_error("invalid item type",
							DBERR_CLASS_WARNING);
				ip->type = item_none;
				break;
	}

	/* Decode the item name */
	ip->name = (char *)nbmalloc(namelen);
	strncpy(ip->name, buf, namelen - 1);
	ip->name[namelen - 1] = '\0';
	buf += namelen;

	/* Decode the item value */
	datavalid = FALSE;
	switch (ip->type) {
		case item_string:
				if (datalen > 0) {
					ip->val.s = (char *)nbmalloc(datalen);
					strncpy(ip->val.s, buf, datalen - 1);
					ip->val.s[datalen - 1] = '\0';
				}
				datavalid = TRUE;
				break;
		case item_integer:
				if (datalen != sizeof(ip->val.i))
					break;
				ip->val.i = *((long *)buf);
				datavalid = TRUE;
				break;
		case item_boolean:
				if (datalen != sizeof(ip->val.b))
					break;
				ip->val.b = *((int *)buf);
				datavalid = TRUE;
				break;
		case item_none:
				if (datalen > 0)
					break;
				datavalid = TRUE;
				break;
	}
	buf += datalen;

	/* Print error for invalid data */
	if (!datavalid)
		nblib_db_error("invalid item data", DBERR_CLASS_WARNING);
  }
}



/*
 * Encode a database record
 *
 * The data item list passed to this routine remains in the ownership of
 * this module. It gets deleted when the current record position gets
 * changed.
 */
static void encode __F((db, key, data),
				struct dbbdb *db AND
				DBT *key AND
				DBT *data)
{
  struct dbitem *ip;
  char *buf, *dataptr, *nameptr;
  size_t bufsize, recofs, namelen, datalen;
  int itemtype;

  /* First clear all old data */
  memzero(key, sizeof(DBT));
  memzero(data, sizeof(DBT));

  /* Copy the record name out of the database handle */
  assert(db->currec != NULL);
  namelen = strlen(db->currec);
  nameptr = (char *)(key->data);
  copystr(&nameptr, db->currec);
  key->data = (void *)nameptr;
  key->size = (int)(namelen + 1);

  /* Now encode the record data */
  assert(db->curitems != NULL);
  buf = NULL;
  bufsize = 0;
  recofs = 0;
  ip = db->curitems;
  while (ip != NULL) {
	/* Encode item type */
	namelen = strlen(ip->name) + 1;
	switch (ip->type) {
		case item_string:
			itemtype = DBTYPE_STRING;
			if (ip->val.s == NULL) {
				dataptr = NULL;
				datalen = 0;
			} else {
				dataptr = ip->val.s;
				datalen = strlen(ip->val.s) + 1;
			}
			break;
		case item_boolean:
			itemtype = DBTYPE_BOOLEAN;
			dataptr = (char *)&(ip->val.b);
			datalen = sizeof(ip->val.b);
			break;
		case item_integer:
			itemtype = DBTYPE_INTEGER;
			dataptr = (char *)&(ip->val.i);
			datalen = sizeof(ip->val.i);
			break;
		case item_none:
		default:
			itemtype = DBTYPE_NONE;
			dataptr = NULL;
			datalen = 0;
			break;
	}

	/* Allocate enough space for new item and setup item info */
	bufsize += namelen + datalen + 5;
	buf = (char *)nbrealloc(buf, bufsize);
	buf[recofs + 0] = (char)(itemtype & 0xFF);
	buf[recofs + 1] = (char)(namelen & 0xFF);
	buf[recofs + 2] = (char)((namelen / 256) & 0xFF);
	buf[recofs + 3] = (char)(datalen & 0xFF);
	buf[recofs + 4] = (char)((datalen / 256) & 0xFF);
	recofs += 5;

	/* Save item name into record */
	memcpy(&(buf[recofs]), ip->name, namelen);
	recofs += namelen;

	/* Save item data into record */
	if (datalen > 0) {
		memcpy(&(buf[recofs]), dataptr, datalen);
		recofs += datalen;
	}

	/* Proceed with next item */
	ip = ip->next;
  }

  /* Setup the record data information */
  data->data = (voidstar)buf;
  data->size = bufsize;
}



/*
 * Check for an error return, and call the error handler. This function
 * returns FALSE, if an error occurred.
 */
static inline int checkerr __F((errnum, msg), int errnum AND const char *msg)
{
  if (errnum != 0) {
	(dbenv->err)(dbenv, errnum, msg);
	return(FALSE);
  }
  return(TRUE);
}



/*
 * Error callback function for Berkeley DB functions
 */
static void bdberror __F((env, prefix, msg),
#if (DB_VERSION_MAJOR == 4 && DB_VERSION_MINOR >= 3) || DB_VERSION_MAJOR > 4
				const DB_ENV *env AND
				const char *prefix AND
				const char *msg
#else
				const char *prefix AND
				      char *msg
#endif
							)
{
  nblib_db_error(msg, DBERR_CLASS_ERROR);
}



/*
 * Error callback function for fatal errors
 */
static void bdbpanic __F((env, errval), DB_ENV *env AND int errval)
{
  nblib_db_error(db_strerror(errval), DBERR_CLASS_FATAL);
}



/*
 * Close database environment
 */
static void closeenv __F_NOARGS
{
  if (dbenv != NULL) {
	(void)(dbenv->close)(dbenv, 0);
	dbenv = NULL;
  }
}



/*
 * Initialize the database environment
 */
static int initmodule __F_NOARGS
{
  int flags, v_major, v_minor, v_patch;

  /* Check if we are initialized already */
  if (dbenv != NULL)
	return(TRUE);

  /* Check that the version numbers match */
  (void)db_version(&v_major, &v_minor, &v_patch);
  if (v_major != DB_VERSION_MAJOR ||
      v_minor != DB_VERSION_MINOR ||
      v_patch != DB_VERSION_PATCH) {
	nblib_db_error("Berkeley DB library version mismatch",
							DBERR_CLASS_FATAL);
	return(FALSE);
  }

  /* Use replacement allocation routines */
  (void)db_env_set_func_malloc(&nbmalloc);
  (void)db_env_set_func_realloc(&nbrealloc);

  /* Create a new database environment */
  if (db_env_create(&dbenv, 0) != 0) {
	nblib_db_error("unable to create database environment",
							DBERR_CLASS_FATAL);
	return(FALSE);
  }

  /* Set various default parameters for error handling */
  (dbenv->set_errpfx)(dbenv, "");
  (dbenv->set_errcall)(dbenv, &bdberror);
  (dbenv->set_paniccall)(dbenv, &bdbpanic);

  /* Set some default values */
  (dbenv->set_tmp_dir)(dbenv, nbtempdir);
  (dbenv->set_data_dir)(dbenv, nbhomedir);
  (dbenv->set_lk_detect)(dbenv, DB_LOCK_DEFAULT);
  (dbenv->set_timeout)(dbenv, DEFAULT_TIMEOUT, DB_SET_LOCK_TIMEOUT);

  /* Open the database environment */
  flags = DB_INIT_MPOOL | DB_INIT_CDB | DB_CREATE | DB_PRIVATE;
  if ((dbenv->open)(dbenv, nbhomedir, flags, 0) != 0) {
	nblib_db_error("unable to open database environment",
							DBERR_CLASS_FATAL);
	closeenv();
	return(FALSE);
  }

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



/*
 * Open a new cursor
 */
static int opencursor __F((db), struct dbbdb *db)
{
  int flags, errnum;

  /* Check if the cursor is already open */
  if (db->cursor != NULL)
	return(TRUE);

  /* Otherwise open a new cursor */
  flags = 0;
  if ((db->flags & DBFLAGS_RO) == 0)
	flags |= DB_WRITECURSOR;
  errnum = (db->db->cursor)(db->db, NULL, &(db->cursor), flags);
  if (!checkerr(errnum, "unable to open database cursor"))
	return(FALSE);

  /* Set the cursor to the very beginning of the database */
  db->curpos = CURPOS_BOF;
  return(TRUE);
}



/*
 * Close a cursor
 */
static int closecursor __F((cursor), DBC **cursor)
{
  int errnum;

  if (cursor != NULL && *cursor != NULL) {
	errnum = ((*cursor)->c_close)(*cursor);
	if (!checkerr(errnum, "unable to close database cursor"))
		return(FALSE);
	*cursor = NULL;
  }
  return(TRUE);
}



/*
 * Open a Berkeley DB database
 */
static DBID bdb_open __F((conf, flags), struct dbconfig *conf AND int flags)
{
  struct dbbdb *db = NULL;
  char *fname = NULL;
  char *dirname = NULL;
  char *cp;
  int errnum, class;

  /* Initialize the module */
  if (!initmodule())
	return(DBID_NULL);

  /* Check for flags */
  if (conf->mode == dbmode_ro) {
	/* This is just for safety - should have been done already */
	flags |= DBFLAGS_RO;
	flags &= ~DBFLAGS_ADD;
  }

  /* Check for file name */
  copystr(&fname, conf->config.bdb.fname);
  checkaccess(&fname, nbhomedir, ACCESS_FILE_READ);
  if (fname == NULL) {
	/* If the file is readonly and optional, we can return without error */
	if ((flags & (DBFLAGS_OPT | DBFLAGS_RO)) == (DBFLAGS_OPT | DBFLAGS_RO)) {
		class = DBERR_CLASS_INFO;
		goto openerr1;
	}

	/* If the file is readonly and non-optional, that's an error */
	if ((flags & (DBFLAGS_OPT | DBFLAGS_RO)) == DBFLAGS_RO) {
		class = DBERR_CLASS_ERROR;
openerr1:
		nblib_db_error("database file not found", class);
		goto openend;
	}

	/* Otherwise we have to provide a suitable file name */
	copystr(&fname, conf->config.bdb.fname);
	setpath(&fname, nbhomedir);
	assert(fname != NULL);
	if ((cp = strchr(fname, ':')) != NULL)
		*cp = '\0';
	if (!*fname) {
		class = DBERR_CLASS_ERROR;
		goto openerr1;
	}
  }

  /* Check for database file directory */
  copystr(&dirname, fname);
  if ((cp = strrchr(dirname, '/')) == NULL)
	copystr(&dirname, nbhomedir);
  else {
	*cp = '\0';
	checkaccess(&dirname, nbhomedir,
			((flags & DBFLAGS_RO) == 0 ?
					ACCESS_DIR_RW : ACCESS_DIR_READ));
  }
  if (dirname == NULL) {
	nblib_db_error("unable to access directory for database file",
							DBERR_CLASS_ERROR);
	goto openend;
  }

  /* Create a new database entity */
  assert(conf->type == dbtype_bdb);
  db = (struct dbbdb *)nbmalloc(sizeof(struct dbbdb));
  db->flags = flags;
  db->config = conf;
  db->curpos = CURPOS_BOF;
  db->currec = NULL;
  db->curitems = NULL;
  db->cursor = NULL;
  db->mark = NULL;

  /* Create a new Berkeley DB entry */
  errnum = db_create(&(db->db), dbenv, 0);
  if (!checkerr(errnum, "unable to create database structure"))
	goto openerr2;

  /* Set database flags */
  errnum = (db->db->set_flags)(db->db, DB_DUP);
  if (!checkerr(errnum, "unable to set database flags"))
	goto openerr3;

  /* Open the database */
  flags = 0;
  if ((db->flags & DBFLAGS_RO) != 0)
	flags |= DB_RDONLY;
  else if ((db->flags & DBFLAGS_OPT) != 0)
	flags |= DB_CREATE;
  errnum = (db->db->open)(db->db, NULL, fname, NULL, DB_BTREE, flags, 0);
  if (!checkerr(errnum, "unable to open database")) {
openerr3:
	(void)(db->db->close)(db->db, 0);
openerr2:
	free(db);
	db = NULL;
  }

  /* Return database handle */
openend:
  if (db != NULL)
	opencount++;
  if (fname != NULL)
	free(fname);
  if (dirname != NULL)
	free(dirname);
  return(db == NULL ? DBID_NULL : (DBID)db);
}



/*
 * Close a Berkeley DB database file
 */
static void bdb_close __F((dbid), DBID dbid)
{
  struct dbbdb *db = (struct dbbdb *)dbid;

  /* Just for safety */
  assert(opencount > 0);

  /* Close the bookmark */
  (void)closecursor(&(db->mark));

  /* Close the database cursor */
  (void)closecursor(&(db->cursor));

  /* Close the database */
  (void)(db->db->close)(db->db, 0);

  /* Free the database handle */
  cleardb(db, FALSE);
  free(db);

  /* Check if we have to close the environment */
  if (--opencount == 0)
	closeenv();
}



/*
 * Release a Berkeley DB cursor
 */
static void bdb_release __F((dbid), DBID dbid)
{
  struct dbbdb *db = (struct dbbdb *)dbid;

  db->curpos = CURPOS_BOF;
  (void)closecursor(&(db->mark));
  (void)closecursor(&(db->cursor));
}



/*
 * General routine to read data from the database. It returns TRUE if
 * a record could be read.
 */
static int doget __F((db, recname, flags),
				struct dbbdb *db AND
				const char *recname AND
				int flags)
{
  DBT key, data;
  char *cp, *buf = NULL;
  int errnum, ret = FALSE;
  size_t len = 0;

  /* Check for valid cursor position */
  if (db->curpos != CURPOS_VALID || db->cursor == NULL)
	goto dogetend;

  /*
   * The Berkeley-DB only compares the beginning of a string and does not
   * understand wildcards. Therefore, to generate the search key we extract
   * the beginning part of the search string, which does not contain any
   * wildcard characters. This is only necessary for DB_SET_RANGE, because
   * DB_SET always uses the full key, and all other access methods don't
   * even use the key at all (except for DB_BOTH and variants, which we
   * don't use).
   */
  if (flags == DB_SET || flags == DB_SET_RANGE) {
	if (recname == NULL || !*recname)
		goto dogetend;
	copystr(&buf, recname);
	if (flags == DB_SET_RANGE) {
		for (cp = buf; *cp; cp++) {
			if (*cp == DBMATCH_SINGLE || *cp == DBMATCH_MULTI) {
				*cp = '\0';
				break;
			}
		}
	}
	len = strlen(buf);
	if (flags == DB_SET && len == 0)
		goto dogetend;
  }

  /*
   * Now ask the Berkeley-DB for the data. If we used a partial key, check
   * the actual record name, and if it doesn't match continue the search.
   */
  while (TRUE) {
	/* Generate search key */
	memzero(&key, sizeof(key));
	if (flags == DB_SET || flags == DB_SET_RANGE) {
		key.data = buf;
		key.size = len + 1;
	}

	/* Get data from Berkeley-DB */
	memzero(&data, sizeof(data));
	errnum = (db->cursor->c_get)(db->cursor, &key, &data, flags);
	if (errnum == DB_NOTFOUND || errnum == DB_KEYEMPTY)
		goto dogetend;
	if (!checkerr(errnum, "unable to read from database"))
		goto dogetend;
	if (key.data == NULL || key.size == 0)
		goto dogetend;
	if (recname == NULL || !*recname || flags == DB_SET)
		break;

	/* Check if key matches */
	cp = (char *)(key.data);
	if (cp[key.size - 1] == '\0') {
		if (len > 0 && (strlen(cp) < len || strncmp(buf, cp, len)))
			goto dogetend;
		if (nblib_db_match(recname, cp))
			break;
	}
	flags = DB_NEXT_NODUP;
  }

  /* Decode the record data */
  decode(db, &key, &data);
  ret = TRUE;

dogetend:
  if (buf != NULL)
	free(buf);
  return(ret);
}



/*
 * Find a record
 */
static int bdb_find __F((dbid, recname, first),
				DBID dbid AND
				const char *recname AND
				int first)
{
  struct dbbdb *db = (struct dbbdb *)dbid;
  int mode;

  /* Open a new database cursor if not open already */
  if (!opencursor(db))
	return(FALSE);

  /* Determine search mode */
  if (first || db->curpos == CURPOS_BOF)
	mode = DB_SET_RANGE;
  else if (db->curpos == CURPOS_EOF)
	return(FALSE);
  else
	mode = DB_NEXT;

  /* Search next record */
  db->curpos = CURPOS_VALID;
  if (!doget(db, recname, mode))
	db->curpos = CURPOS_EOF;
  return(db->curpos == CURPOS_VALID ? TRUE : FALSE);
}



/*
 * Advance to next record
 */
static int bdb_next __F((dbid), DBID dbid)
{
  struct dbbdb *db = (struct dbbdb *)dbid;
  int mode;

  /* Open a new database cursor if not open already */
  if (!opencursor(db))
	return(FALSE);

  /* Determine search mode */
  if (db->curpos == CURPOS_BOF)
	mode = DB_FIRST;
  else if (db->curpos == CURPOS_EOF)
	return(FALSE);
  else
	mode = DB_NEXT;

  /* Find next record */
  db->curpos = CURPOS_VALID;
  if (!doget(db, NULL, mode))
	db->curpos = CURPOS_EOF;
  return(db->curpos == CURPOS_VALID ? TRUE : FALSE);
}



/*
 * Advance to previous record
 */
static int bdb_prev __F((dbid), DBID dbid)
{
  struct dbbdb *db = (struct dbbdb *)dbid;
  int mode;

  /* Open a new database cursor if not open already */
  if (!opencursor(db))
	return(FALSE);

  /* Determine search mode */
  if (db->curpos == CURPOS_BOF)
	return(FALSE);
  else if (db->curpos == CURPOS_EOF)
	mode = DB_LAST;
  else
	mode = DB_PREV;

  /* Find previous record */
  db->curpos = CURPOS_VALID;
  if (!doget(db, NULL, mode))
	db->curpos = CURPOS_BOF;
  return(db->curpos == CURPOS_VALID ? TRUE : FALSE);
}



/*
 * Advance to first record
 */
static int bdb_first __F((dbid), DBID dbid)
{
  struct dbbdb *db = (struct dbbdb *)dbid;

  /* Open a new database cursor if not open already */
  if (!opencursor(db))
	return(FALSE);

  /* Get first database record */
  db->curpos = CURPOS_VALID;
  if (!doget(db, NULL, DB_FIRST))
	db->curpos = CURPOS_BOF;
  return(db->curpos == CURPOS_VALID ? TRUE : FALSE);
}



/*
 * Advance to last record
 */
static int bdb_last __F((dbid), DBID dbid)
{
  struct dbbdb *db = (struct dbbdb *)dbid;

  /* Open a new database cursor if not open already */
  if (!opencursor(db))
	return(FALSE);

  /* Get last database record */
  db->curpos = CURPOS_VALID;
  if (!doget(db, NULL, DB_LAST))
	db->curpos = CURPOS_EOF;
  return(db->curpos == CURPOS_VALID ? TRUE : FALSE);
}



/*
 * Return name of current record
 */
static char *bdb_curpos __F((dbid), DBID dbid)
{
  struct dbbdb *db = (struct dbbdb *)dbid;
  char *ret = NULL;

  if (db->curpos == CURPOS_VALID && db->cursor != NULL)
	copystr(&ret, db->currec);
  return(ret);
}



/*
 * Mark the current record
 */
static int bdb_mark __F((dbid, action), DBID dbid AND int action)
{
  struct dbbdb *db = (struct dbbdb *)dbid;
  DBC *oldcursor;
  int errnum, oldcurpos;

  /* Open a new database cursor if not open already */
  if (!opencursor(db))
	return(FALSE);

  /* Execute required action */
  switch (action) {
	case BOOKMARK_SET:
		/* Check if the current cursor position is valid */
		if (db->curpos != CURPOS_VALID || db->cursor == NULL) {
			nblib_db_error("invalid record position for bookmark",
							DBERR_CLASS_ERROR);
			break;
		}

		/* Release any old bookmark */
		if (!closecursor(&(db->mark)))
			break;

		/* Clone current cursor into bookmark */
		errnum = (db->cursor->c_dup)(db->cursor, &(db->mark),
								DB_POSITION);
		if (!checkerr(errnum, "unable to set database bookmark"))
			break;
		return(TRUE);

	case BOOKMARK_RELEASE:
		/* Just release the old bookmark */
		if (!closecursor(&(db->mark)))
			break;
		return(TRUE);

	case BOOKMARK_JUMP:
		/* Check if we have anything to do */
		if (db->mark == NULL)
			break;

		/*
		 * Read the contents of the record to which the bookmark
		 * points. If the record has been deleted previously, we
		 * have to restore the old cursor data and re-read the
		 * current record. This is necessary because there is no
		 * way with Berkeley DB to compare the position of two
		 * cursors in the bdb_delete() routine (as is done with
		 * the other database drivers).
		 */
		oldcursor = db->cursor;
		oldcurpos = db->curpos;
		db->cursor = db->mark;
		db->curpos = CURPOS_VALID;
		if (!doget(db, NULL, DB_CURRENT)) {
			db->cursor = oldcursor;
			db->curpos = oldcurpos;
			(void)closecursor(&(db->mark));
			if (!doget(db, NULL, DB_CURRENT))
				db->curpos = CURPOS_EOF;
			break;
		}
		db->mark = NULL;

		/* Close current cursor */
		if (!closecursor(&oldcursor))
			break;
		return(TRUE);

	default:
		nblib_db_error("invalid bookmark action code", DBERR_CLASS_ERROR);
		break;
  }
  return(FALSE);
}



/*
 * Delete current record
 */
static void bdb_delete __F((dbid), DBID dbid)
{
  struct dbbdb *db = (struct dbbdb *)dbid;
  int errnum;

  /* This should have been checked in the upper levels already */
  assert((db->flags & DBFLAGS_RO) == 0);

  /* Unable to delete the beginning and end of file indications */
  if (db->curpos != CURPOS_VALID || db->cursor == NULL) {
	nblib_db_error("invalid database delete position", DBERR_CLASS_ERROR);
	return;
  }

  /* Delete the current record */
  errnum = (db->cursor->c_del)(db->cursor, 0);
  if (!checkerr(errnum, "unable to delete record"))
	return;

 /* Move cursor to next record */
  if (!doget(db, NULL, DB_NEXT))
	db->curpos = CURPOS_EOF;
}



/*
 * Read a record from the current read position
 */
static struct dbitem *bdb_read __F((dbid, recname),
				DBID dbid AND
				char **recname)
{
  struct dbbdb *db = (struct dbbdb *)dbid;

  /* Check for valid read position */
  if (db->curpos != CURPOS_VALID || db->cursor == NULL) {
	nblib_db_error("invalid database read position", DBERR_CLASS_ERROR);
	return(NULL);
  }

  /* Return the data which has been read already */
  copystr(recname, db->currec);
  return(db->curitems);
}



/*
 * Write a record into the current position
 */
static void bdb_write __F((dbid, items),
				DBID dbid AND
				struct dbitem *items)
{
  struct dbbdb *db = (struct dbbdb *)dbid;
  DBT key, data;
  int errnum;

  /* This should have been checked in the upper levels already */
  assert((db->flags & DBFLAGS_RO) == 0);

  /* Empty records are not allowed */
  assert(items != NULL);

  /* Check if we have a current read position */
  if (db->curpos != CURPOS_VALID || db->cursor == NULL) {
	nblib_db_error("invalid database write position", DBERR_CLASS_ERROR);
	return;
  }

  /* Generate new record information */
  cleardb(db, TRUE);
  db->curitems = items;
  encode(db, &key, &data);

  /* Add new record to database */
  errnum = (db->cursor->c_put)(db->cursor, &key, &data, DB_CURRENT);
  if (!checkerr(errnum, "unable to write record"))
	db->curpos = CURPOS_EOF;

  /* Free all data structures */
  free(key.data);
  free(data.data);
}



/*
 * Add a new record
 */
static void bdb_add __F((dbid, recname, items),
				DBID dbid AND
				const char *recname AND
				struct dbitem *items)
{
  struct dbbdb *db = (struct dbbdb *)dbid;
  DBT key, data;
  int errnum;

  /* This should have been checked in the upper levels already */
  assert((db->flags & (DBFLAGS_ADD | DBFLAGS_RO)) == DBFLAGS_ADD);

  /* Empty records are not allowed */
  assert(items != NULL);

  /* Check if we have a valid cursor */
  if (db->cursor == NULL) {
	nblib_db_error("unable to add new record", DBERR_CLASS_ERROR);
	return;
  }

  /* Generate new record information */
  cleardb(db, FALSE);
  copystr(&(db->currec),recname);
  db->curitems = items;
  encode(db, &key, &data);

  /* Add new record to database */
  errnum = (db->cursor->c_put)(db->cursor, &key, &data, DB_KEYLAST);
  if (!checkerr(errnum, "unable to add new record"))
	db->curpos = CURPOS_EOF;

  /* Free all data structures */
  free(key.data);
  free(data.data);
}



/*
 * Routine to determine the location of an error
 * This always returns NULL to use the default handling
 */
static char *bdb_errmsg __F((dbid, recname, item),
				DBID dbid AND
				const char *recname AND
				const struct dbitem *item)
{
  return(NULL);
}



/*
 * Structure to export all Berkeley DB database functions
 */
struct dbprocs db_bdbprocs = {
	&bdb_open,
	&bdb_find,
	&bdb_next,
	&bdb_prev,
	&bdb_first,
	&bdb_last,
	&bdb_curpos,
	&bdb_mark,
	&bdb_delete,
	&bdb_read,
	&bdb_write,
	&bdb_add,
	&bdb_release,
	&bdb_close,
	&bdb_errmsg
};

#endif /* HAVE_BDB */

