/*-
 * See the file LICENSE for redistribution information.
 *
 * Copyright (c) 1996, 1997, 1998
 *	Sleepycat Software.  All rights reserved.
 */

#include "config.h"

#ifndef lint
static const char sccsid[] = "@(#)utils.c	10.69 (Sleepycat) 12/16/98";
#endif /* not lint */

/*
 * This file is divided up into 4 sets of functions:
 * 1. The dbopen command and its support functions.
 * 2. The dbwidget and dbcursor commands.
 * 3. The db support functions (e.g. get, put, del)
 * 4. The cursor support functions (e.g. get put, del)
 */

#ifndef NO_SYSTEM_INCLUDES
#include <sys/types.h>

#include <errno.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <unistd.h>
#endif
#include <tcl.h>

#include "db_int.h"
#include "db_page.h"
#include "hash.h"
#include "dbtest.h"
#include "test_ext.h"

/* Internal functions */
int db_del_cmd __P((Tcl_Interp *interp, int argc, char *argv[], DB *dbp));
int db_get_cmd __P((Tcl_Interp *interp, int argc, char *argv[], DB *dbp));
int db_getbin_cmd __P((Tcl_Interp *interp, int argc, char *argv[], DB *dbp));
int db_join_cmd __P((Tcl_Interp *interp, int argc, char *argv[], DB *dbp));
int db_put_cmd __P((Tcl_Interp *interp, int argc, char *argv[], DB *dbp));
int db_putbin_cmd __P((Tcl_Interp *interp, int argc, char *argv[], DB *dbp));
int dbc_del_cmd __P((Tcl_Interp *interp, int argc, char *argv[], DBC *dbc));
int dbc_get_cmd __P((Tcl_Interp *interp, int argc, char *argv[], DBC *dbc));
int dbc_getbin_cmd __P((Tcl_Interp *interp, int argc, char *argv[], DBC *dbc));
int dbc_put_cmd __P((Tcl_Interp *interp, int argc, char *argv[], DBC *dbc));
int dbt_from_file __P((Tcl_Interp *interp, char *file, DBT *dbt));
int dbt_to_file __P((Tcl_Interp *interp, char *file, DBT *dbt));
u_int8_t *list_to_numarray __P((Tcl_Interp *, char *));
void set_get_result __P((Tcl_Interp *interp, DBT *dbt));

/*
 * XXX
 * Freeing memory allocated by the TCL library currently fails on Win*,
 * probably due to a mismatch of malloc/free implementations between
 * the TCL library and this module.  Sidestep the issue for now by not
 * freeing on Win* platforms.
 */
#ifdef _WIN32
#define	FREE_TCL(x)
#else
#define	FREE_TCL(x)	free(x)
#endif

/*
 * dbopen_cmd --
 *	Implements dbopen for dbtest.  Dbopen creates a widget that
 * implements all the commands found off the DB structure.
 */

#define DBOPEN_USAGE "dbopen file flags mode type [options]\n\toptions:\n\t"

int
dbopen_cmd(notused, interp, argc, argv)
	ClientData notused;
	Tcl_Interp *interp;
	int argc;
	char *argv[];
{
	static int db_number = 0;
	static const struct {
		char *str;
		DBTYPE type;
	} list[] = {
		{"DB_UNKNOWN",	DB_UNKNOWN},
		{"db_unknown",	DB_UNKNOWN},
		{"UNKNOWN",	DB_UNKNOWN},
		{"unknown",	DB_UNKNOWN},
		{"DB_BTREE",	DB_BTREE},
		{"db_btree",	DB_BTREE},
		{"BTREE",	DB_BTREE},
		{"btree",	DB_BTREE},
		{"DB_HASH",	DB_HASH},
		{"db_hash",	DB_HASH},
		{"HASH",	DB_HASH},
		{"hash",	DB_HASH},
		{"DB_RECNO",	DB_RECNO},
		{"db_recno",	DB_RECNO},
		{"RECNO",	DB_RECNO},
		{"recno",	DB_RECNO},
		{ 0 }
	}, *lp;
	DB *dbp;
	DBTYPE type;
	DB_ENV *env;
	DB_INFO *openinfo;
	u_int32_t flags;
	int mode, ret, tclint;
	char dbname[50], *name;

	notused = NULL;

	/* Check number of arguments. */
	USAGE_GE(argc, 5, DBOPEN_USAGE, DO_INFO);

	/* Check flags and mode. */
	if (Tcl_GetInt(interp, argv[2], &tclint) != TCL_OK)
		goto usage;
	flags = (u_int32_t)tclint;
	if (Tcl_GetInt(interp, argv[3], &mode) != TCL_OK) {
usage:		Tcl_AppendResult(interp,
		    "\nUsage: ", DBOPEN_USAGE, DB_INFO_FLAGS, NULL);
		return (TCL_OK);
	}

	name = strcmp(argv[1], "NULL") == 0 ? NULL : argv[1];

	process_am_options(interp, argc - 5, &argv[5], &openinfo);
	process_env_options(interp, argc - 5, &argv[5], &env);

	/* Figure out type. */
	COMPQUIET(type, DB_HASH);
	for (lp = list; lp->str != NULL; ++lp)
		if (strcmp(argv[4], lp->str) == 0) {
			type = lp->type;
			break;
		}
	if (lp->str == NULL) {
		Tcl_SetResult(interp, "Invalid type", TCL_STATIC);
		return (TCL_ERROR);
	}

	if (openinfo == NULL && type != DB_UNKNOWN) {
		Tcl_AppendResult(interp, "Usage: ", DBOPEN_USAGE,
		    DB_INFO_FLAGS, NULL);
		return (TCL_ERROR);
	}

	debug_check();

	/* Call dbopen. */
	ret = db_open(name, type, flags, mode, env, openinfo, &dbp);
	if (openinfo && openinfo->re_source)
		free(openinfo->re_source);
	if (openinfo)
		free(openinfo);
	if (ret != 0) {
		Tcl_SetResult(interp, "dbopen: ", TCL_STATIC);
		errno = ret;
		Tcl_AppendResult(interp, Tcl_PosixError(interp), NULL);
		return (TCL_OK);
	}

	/* Create widget command. */
	/* Create new command name. */
	snprintf(dbname, sizeof(dbname), "db%d", db_number);
	db_number++;

	Tcl_CreateCommand(interp, dbname, dbwidget_cmd, (ClientData)dbp, NULL);
	Tcl_SetResult(interp, dbname, TCL_VOLATILE);
	return (TCL_OK);
}

/*
 * process_am_options --
 *	Read the options in the command line and create a DB_INFO structure
 *	to pass to db_open.
 */
int
process_am_options(interp, argc, argv, openinfo)
	Tcl_Interp *interp;
	int argc;
	char *argv[];
	DB_INFO **openinfo;
{
	DB_INFO *oi;
	int err, tclint;
	char *option;

	COMPQUIET(option, NULL);

	err = TCL_OK;
	oi = (DB_INFO *)calloc(sizeof(DB_INFO), 1);

	while (argc >= 1) {
		if (**argv != '-') {		/* Make sure it's an option */
			argc--;
			argv++;
			continue;
		}
		/* Set option to first character after "-" */
		option = argv[0];
		option++;

		if (argc == 1)
			break;

		if (strcmp(option, "flags") == 0) {
	/* Contains flags for all access methods */
	    		if ((err =
	    		    Tcl_GetInt(interp, argv[1], &tclint)) != TCL_OK)
				break;
			oi->flags = (u_int32_t)tclint;
		} else if (strcmp(option, "psize") == 0) {
	    		if ((err =
	    		    Tcl_GetInt(interp, argv[1], &tclint)) != TCL_OK)
				break;
			oi->db_pagesize = (size_t)tclint;
		} else if (strcmp(option, "order") == 0) {
	    		if ((err =
	    		    Tcl_GetInt(interp, argv[1], &tclint)) != TCL_OK)
				break;
			oi->db_lorder = (int)tclint;
		} else if (strcmp(option, "cachesize") == 0) {
	    		if ((err =
	    		    Tcl_GetInt(interp, argv[1], &tclint)) != TCL_OK)
				break;
			oi->db_cachesize = (size_t)tclint;
		} else if (strcmp(option, "minkey") == 0) {
	/* Btree flags */
	    		if ((err =
	    		    Tcl_GetInt(interp, argv[1], &tclint)) != TCL_OK)
				break;
			oi->bt_minkey = (u_int32_t)tclint;
		} else if (strcmp(option, "compare") == 0) {
			/* Not sure how to handle this. */
			err = TCL_ERROR;
		} else if (strcmp(option, "prefix") == 0) {
			/* Not sure how to handle this. */
			err = TCL_ERROR;
		} else if (strcmp(option, "ffactor") == 0) {
	/* Hash flags */
	    		if ((err =
	    		    Tcl_GetInt(interp, argv[1], &tclint)) != TCL_OK)
				break;
			oi->h_ffactor = (u_int32_t)tclint;
		} else if (strcmp(option, "nelem") == 0) {
	    		if ((err =
	    		    Tcl_GetInt(interp, argv[1], &tclint)) != TCL_OK)
				break;
			oi->h_nelem = (u_int32_t)tclint;
		} else if (strcmp(option, "hash") == 0) {
	    		if ((err =
	    		    Tcl_GetInt(interp, argv[1], &tclint)) != TCL_OK)
				break;
			switch (tclint) {
				case 2: oi->h_hash = __ham_func2; break;
				case 3: oi->h_hash = __ham_func3; break;
				case 4: oi->h_hash = __ham_func4; break;
				case 5: oi->h_hash = __ham_func5; break;
			}
			if (oi->h_hash == NULL) {
				err = TCL_ERROR;
				break;
			}
		} else if (strcmp(option, "recdelim") == 0) {
	/* Recno flags */
	    		if ((err =
	    		    Tcl_GetInt(interp, argv[1], &tclint)) != TCL_OK)
				break;
			oi->re_delim = (int)tclint;
		} else if (strcmp(option, "recpad") == 0) {
	    		if ((err =
	    		    Tcl_GetInt(interp, argv[1], &tclint)) != TCL_OK)
				break;
			oi->re_pad = (int)tclint;
		} else if (strcmp(option, "reclen") == 0) {
	    		if ((err =
	    		    Tcl_GetInt(interp, argv[1], &tclint)) != TCL_OK)
				break;
			oi->re_len = (u_int32_t)tclint;
		} else if (strcmp(option, "recsrc") == 0)
			 (void)__os_strdup(argv[1], &oi->re_source);

		argc -= 2;
		argv += 2;
	}
	if (err != TCL_OK) {
		Tcl_AppendResult(interp,
		    "\nInvalid ", option, " value: ", argv[1], "\n", NULL);
		free(oi);
		oi = NULL;
	}
	*openinfo = oi;
	return (oi ? 0 : 1);
}

/*
 * process_env_options --
 *	Read the options in the command line and create a DB_ENV structure
 *	to pass to db_open.
 */
int
process_env_options(interp, argc, argv, envinfo)
	Tcl_Interp *interp;
	int argc;
	char *argv[];
	DB_ENV **envinfo;
{
	DB_ENV *env;
	Tcl_CmdInfo info;
	u_int32_t flags;
	int err, nconf, tclint;
	char *option, *db_home, **config;
	u_int8_t *conflicts;

	COMPQUIET(option, NULL);
	err = TCL_OK;
	flags = 0;
	db_home = Tcl_GetVar(interp, "testdir", 0);
	config = NULL;
	conflicts = NULL;

	env = (DB_ENV *)calloc(sizeof(DB_ENV), 1);
	while (argc > 1) {
		if (**argv != '-') {		/* Make sure it's an option */
			argc--;
			argv++;
			continue;
		}
		/* Set option to first character after "-" */
		option = argv[0];
		option++;

		if (argv[1] == NULL || argv[1][0] == '-') {
			Tcl_SetResult(interp, "Invalid options: ",
			    TCL_STATIC);
			Tcl_AppendResult(interp, argv[0],
			   "requires parameter", 0);
			return (TCL_ERROR);
		}

		if (strcmp(option, "dbenv") == 0) {
	/* environment already set up. */
			if (Tcl_GetCommandInfo(interp, argv[1], &info) == 0) {
				Tcl_SetResult(interp,
				    "Invalid environment: ", TCL_STATIC);
				Tcl_AppendResult(interp, argv[1], 0);
				return (TCL_ERROR);
			}
			free(env);
			*envinfo = (DB_ENV *)info.clientData;
			return (TCL_OK);
		} else if (strcmp(option, "dbflags") == 0) {
	/* db_appinit parameters */
	    		if ((err =
	    		    Tcl_GetInt(interp, argv[1], &tclint)) != TCL_OK)
				break;
			flags = (u_int32_t)tclint;
			/*
			 * Don't specify DB_THREAD if the architecture can't
			 * do spinlocks.
			 */
#ifndef HAVE_SPINLOCKS
			LF_CLR(DB_THREAD);
#endif
		} else if (strcmp(option, "dbhome") == 0) {
			db_home = argv[1];
		} else if (strcmp(option, "dbconfig") == 0) {
			err = Tcl_SplitList(interp, argv[1], &nconf, &config);
			if (err)
				break;
		} else if (strcmp(option, "maxlocks") == 0) {
	/* Lock flags */
	    		if ((err =
	    		    Tcl_GetInt(interp, argv[1], &tclint)) != TCL_OK)
				break;
			env->lk_max = (u_int32_t)tclint;
		} else if (strcmp(option, "nmodes") == 0) {
	    		if ((err =
	    		    Tcl_GetInt(interp, argv[1], &tclint)) != TCL_OK)
				break;
			env->lk_modes = (u_int32_t)tclint;
		} else if (strcmp(option, "detect") == 0) {
	    		if ((err =
	    		    Tcl_GetInt(interp, argv[1], &tclint)) != TCL_OK)
				break;
			env->lk_detect = (u_int32_t)tclint;
		} else if (strcmp(option, "conflicts") == 0) {
			conflicts = list_to_numarray(interp, argv[1]);
			if (conflicts == NULL)
				break;
			env->lk_conflicts = conflicts;
		} else if (strcmp(option, "maxsize") == 0) {
	/* Log flags */
	    		if ((err =
	    		    Tcl_GetInt(interp, argv[1], &tclint)) != TCL_OK)
				break;
			env->lg_max = (u_int32_t)tclint;
		} else if (strcmp(option, "cachesize") == 0) {
	/* Mpool flags */
	    		if ((err =
	    		    Tcl_GetInt(interp, argv[1], &tclint)) != TCL_OK)
				break;
			env->mp_size = (size_t)tclint;
		} else if (strcmp(option, "maxtxns") == 0) {
	/* Txn flags */
	    		if ((err =
	    		    Tcl_GetInt(interp, argv[1], &tclint)) != TCL_OK)
				break;
			env->tx_max = (u_int32_t)tclint;
		} else if (strcmp(option, "rinit") == 0) {
	/* Region init */
			if ((err = db_value_set(1, DB_REGION_INIT)) != 0) {
				if (err == EINVAL) {
					Tcl_SetResult(interp,
					    "EINVAL", TCL_STATIC);
					return (TCL_ERROR);
				}
				Tcl_SetResult(interp,
				    "env/rinit:", TCL_STATIC);
				errno = err;
				Tcl_AppendResult(interp,
				    Tcl_PosixError(interp), 0);
				return (TCL_ERROR);
			}
		} else if (strcmp(option, "shmem") == 0) {
	/* Shared memory specification */
			if (strcmp(argv[1], "anon") == 0) {
				if ((err =
				    db_value_set(1, DB_REGION_ANON)) != 0) {
					if (err == EINVAL) {
						Tcl_SetResult(interp,
						    "EINVAL", TCL_STATIC);
						return (TCL_ERROR);
					}
					Tcl_SetResult(interp,
					    "env/shmem:", TCL_STATIC);
					errno = err;
					Tcl_AppendResult(interp,
					    Tcl_PosixError(interp), 0);
					return (TCL_ERROR);
				}
			} else if (strcmp(argv[1], "named") == 0) {
				if ((err =
				    db_value_set(1, DB_REGION_NAME)) != 0) {
					if (err == EINVAL) {
						Tcl_SetResult(interp,
						    "EINVAL", TCL_STATIC);
						return (TCL_ERROR);
					}
					Tcl_SetResult(interp,
					    "env/shmem:", TCL_STATIC);
					errno = err;
					Tcl_AppendResult(interp,
					    Tcl_PosixError(interp), 0);
					return (TCL_ERROR);
				}
			} else {
				Tcl_SetResult(interp,
				    "Invalid shmem option", TCL_STATIC);
				Tcl_AppendResult(interp, argv[1], NULL);
				return (TCL_OK);
			}
		}

		argc -= 2;
		argv += 2;
	}
	if (err != TCL_OK) {
		Tcl_AppendResult(interp, "\nInvalid ", option, " value: ",
		    argv[1], "\n", NULL);
		if (conflicts != NULL)
			free(conflicts);
		free(env);
		env = NULL;
	}

	/*
	 * Set up error stuff.
	 */
	if (env) {
		env->db_errfile = stderr;
		env->db_errpfx = "dbtest";
		env->db_errcall = NULL;
		env->db_verbose = 0;

		if ((errno = db_appinit(db_home, config, env, flags)) != 0) {
			free(env);
			env = NULL;
		}
	}

	if (config)
		FREE_TCL(config);

	*envinfo = env;
	return (env ? 0 : 1);
}

/*
 * dbwidget --
 *	Since dbopen creates a widget, we need a command that then
 * handles all the widget commands.  This is that command.  If we
 * ever add new "methods" we add new widget commands here.
 */
#define DBWIDGET_USAGE "dbN option ?arg arg ...?"
#define DBCLOSE_USAGE "dbN close"
#define DBCURS_USAGE "dbN cursor txn [flags]"
#define DBFD_USAGE "dbN fd"
#define DBSYNC_USAGE "dbN sync flags"
#define	DBLOCKER_USAGE "dbN locker"

int
dbwidget_cmd(cd_dbp, interp, argc, argv)
	ClientData cd_dbp;
	Tcl_Interp *interp;
	int argc;
	char *argv[];
{
	static int curs_id = 0;
	DB *dbp;
	DBC *cursor;
	DB_ENV *env;
	DB_TXN *txnid;
	Tcl_CmdInfo info;
	u_int32_t flags;
	int fd, ret, tclint;
	char cursname[128];
	u_int8_t *conflicts;

	dbp = (DB *)cd_dbp;

	USAGE_GE(argc, 2, DBWIDGET_USAGE, 0);

	Tcl_SetResult(interp, "0", TCL_STATIC);

	if (strcmp(argv[1], "close") == 0) {
		USAGE(argc, 2, DBCLOSE_USAGE, 0);
		env = dbp->dbenv;
		debug_check();
#ifdef STATISTICS
		if (dbp->stat != NULL)
			(void)dbp->stat(dbp, stdout);
#endif
		ret = dbp->close(dbp, 0);
		if (env && !F_ISSET(env, DB_ENV_STANDALONE)) {
			(void)db_appexit(env);
			conflicts = (u_int8_t *)env->lk_conflicts;
			if (conflicts != NULL)
				free(conflicts);
			free(env);
		}
		(void)Tcl_DeleteCommand(interp, argv[0]);
		if (ret < 0)
			Tcl_SetResult(interp, "1", TCL_STATIC);
		else if (ret > 0) {
			Tcl_SetResult(interp, "db_close:", TCL_STATIC);
			errno = ret;
			Tcl_AppendResult(interp, Tcl_PosixError(interp), 0);
		} else
			Tcl_SetResult(interp, "0", TCL_STATIC);
		return (TCL_OK);
	} else if (strcmp(argv[1], "cursor") == 0) {
		USAGE_GE(argc, 3, DBCURS_USAGE, 0);
		snprintf(cursname,
		    sizeof(cursname), "%s.cursor%d", argv[0], curs_id);
		curs_id++;
		if (argv[2][0] == '0' && argv[2][1] == '\0')
			txnid = NULL;
		else {
			if (Tcl_GetCommandInfo(interp, argv[2], &info) == 0) {
				Tcl_SetResult(interp,
				    "db_del: Invalid argument ", TCL_STATIC);
				Tcl_AppendResult(interp, argv[2],
				    " not a transaction.", 0);
				return (TCL_ERROR);
			}
			txnid = (DB_TXN *)info.clientData;
		}
		debug_check();
		if (argc == 4) {
			if (Tcl_GetInt(interp, argv[3], &tclint) != TCL_OK)
				return (TCL_ERROR);
			flags = (u_int32_t)tclint;
		} else
			flags = 0;
		if ((ret = dbp->cursor(dbp, txnid, &cursor, flags)) == 0) {
			Tcl_CreateCommand(interp, cursname, dbcursor_cmd,
			    (ClientData)cursor, NULL);
			Tcl_SetResult(interp, cursname, TCL_VOLATILE);
		} else {
			Tcl_SetResult(interp, "db_cursor:", TCL_STATIC);
			errno = ret;
			Tcl_AppendResult(interp, Tcl_PosixError(interp), 0);
		}
		return (TCL_OK);
	} else if (strcmp(argv[1], "del") == 0) {
		return (db_del_cmd(interp, argc, argv, dbp));
	} else if (strcmp(argv[1], "fd") == 0) {
		USAGE(argc, 2, DBFD_USAGE, 0);
		Tcl_ResetResult(interp);
		debug_check();
		(void)dbp->fd(dbp, &fd);
		/*
		 * !!!
		 * Safe: interp->result is guaranteed to be at least 200 bytes.
		 */
		sprintf(interp->result, "%d", fd);
		return (TCL_OK);
	} else if (strcmp(argv[1], "get") == 0) {
		return (db_get_cmd(interp, argc, argv, dbp));
	} else if (strcmp(argv[1], "bget") == 0) {
		return (db_get_cmd(interp, argc, argv, dbp));
	} else if (strcmp(argv[1], "getn") == 0) {
		return (db_get_cmd(interp, argc, argv, dbp));
	} else if (strcmp(argv[1], "getbin") == 0) {
		return (db_getbin_cmd(interp, argc, argv, dbp));
	} else if (strcmp(argv[1], "getbinkey") == 0) {
		return (db_getbin_cmd(interp, argc, argv, dbp));
	} else if (strcmp(argv[1], "join") == 0) {
		return (db_join_cmd(interp, argc, argv, dbp));
	} else if (strcmp(argv[1], "put") == 0) {
		return (db_put_cmd(interp, argc, argv, dbp));
	} else if (strcmp(argv[1], "putn") == 0) {
		return (db_put_cmd(interp, argc, argv, dbp));
	} else if (strcmp(argv[1], "putbin") == 0) {
		return (db_putbin_cmd(interp, argc, argv, dbp));
	} else if (strcmp(argv[1], "putbinkey") == 0) {
		return (db_putbin_cmd(interp, argc, argv, dbp));
	} else if (strcmp(argv[1], "put0") == 0) {
		return (db_put_cmd(interp, argc, argv, dbp));
	} else if (strcmp(argv[1], "sync") == 0) {
		USAGE(argc, 3, DBSYNC_USAGE, 0);
		if (Tcl_GetInt(interp, argv[2], &tclint) != TCL_OK)
			return (TCL_ERROR);
		flags = (u_int32_t)tclint;
		debug_check();
		if ((ret = dbp->sync(dbp, flags)) < 0)
			Tcl_SetResult(interp, "1", TCL_STATIC);
		else if (ret > 0) {
			Tcl_SetResult(interp, "db_sync:", TCL_STATIC);
			errno = ret;
			Tcl_AppendResult(interp, Tcl_PosixError(interp), 0);
		} else
			Tcl_SetResult(interp, "0", TCL_STATIC);
		return (TCL_OK);
	} else {
		Tcl_SetResult(interp, DBWIDGET_USAGE, TCL_STATIC);
		return (TCL_ERROR);
	}
}

#define DBCURSOR_USAGE "dbN.cursorM ?arg arg ...?"
#define DBCCLOSE_USAGE "dbN.cursorM close"
int
dbcursor_cmd(cd_dbc, interp, argc, argv)
	ClientData cd_dbc;
	Tcl_Interp *interp;
	int argc;
	char *argv[];
{
	DBC *dbc;

	dbc = (DBC *)cd_dbc;
	Tcl_SetResult(interp, "0", TCL_STATIC);

	if (strcmp(argv[1], "close") == 0) {
		USAGE(argc, 2, DBCCLOSE_USAGE, 0);
		debug_check();
		(void)dbc->c_close(dbc);
		return (Tcl_DeleteCommand(interp, argv[0]));
	} else if (strcmp(argv[1], "del") == 0) {
		return (dbc_del_cmd(interp, argc, argv, dbc));
	} else if (strcmp(argv[1], "get") == 0) {
		return (dbc_get_cmd(interp, argc, argv, dbc));
	} else if (strcmp(argv[1], "bget") == 0) {
		return (dbc_get_cmd(interp, argc, argv, dbc));
	} else if (strcmp(argv[1], "getn") == 0) {
		return (dbc_get_cmd(interp, argc, argv, dbc));
	} else if (strcmp(argv[1], "getbin") == 0) {
		return (dbc_getbin_cmd(interp, argc, argv, dbc));
	} else if (strcmp(argv[1], "getbinkey") == 0) {
		return (dbc_getbin_cmd(interp, argc, argv, dbc));
	} else if (strcmp(argv[1], "put") == 0) {
		return (dbc_put_cmd(interp, argc, argv, dbc));
	} else if (strcmp(argv[1], "putn") == 0) {
		return (dbc_put_cmd(interp, argc, argv, dbc));
	} else {
		Tcl_SetResult(interp, DBCURSOR_USAGE, TCL_STATIC);
		return (TCL_ERROR);
	}
	/* NOTREACHED */
}

/* Widget support commands. */

#define DBDEL_USAGE "dbN del txn key flags"
int
db_del_cmd(interp, argc, argv, dbp)
	Tcl_Interp *interp;
	int argc;
	char *argv[];
	DB *dbp;
{
	DBT key;
	DB_TXN *txnid;
	Tcl_CmdInfo info;
	db_recno_t rkey;
	u_int32_t flags;
	int ret, tclint;

	USAGE(argc, 5, DBDEL_USAGE, 0);

	if (Tcl_GetInt(interp, argv[4], &tclint) != TCL_OK) {
		Tcl_AppendResult(interp, "\n", DBDEL_USAGE, NULL);
		return (TCL_ERROR);
	}
	flags = (u_int32_t)tclint;

	memset(&key, 0, sizeof(key));
	if (dbp->type == DB_RECNO) {
		if (Tcl_GetInt(interp, argv[3], &tclint) != TCL_OK)
			return (TCL_ERROR);
		rkey = (db_recno_t)tclint;
		key.data = &rkey;
		key.size = sizeof(db_recno_t);
	} else {
		key.data = argv[3];
		key.size = strlen(argv[3]) + 1;		/* Add NULL on end. */
	}

	if (argv[2][0] == '0' && argv[2][1] == '\0')
		txnid = NULL;
	else {
		if (Tcl_GetCommandInfo(interp, argv[2], &info) == 0) {
			Tcl_SetResult(interp,
			    "db_del: Invalid argument ", TCL_STATIC);
			Tcl_AppendResult(interp, argv[2],
			    " not a transaction.", 0);
			return (TCL_ERROR);
		}
		txnid = (DB_TXN *)info.clientData;
	}

	debug_check();

	if ((ret = dbp->del(dbp, txnid, &key, flags)) == 0)
		return (TCL_OK);
	else if (ret == DB_NOTFOUND) {
		Tcl_ResetResult(interp);
		Tcl_AppendResult(interp, "Key ", argv[3], " not found.", NULL);
		return (TCL_OK);
	} else {
		Tcl_SetResult(interp, "-1", TCL_STATIC);
		return (TCL_OK);
	}
}

#define DBGET_USAGE "dbN get txn key flags [beg len]"
#define DBBGET_USAGE "dbN get txn key data flags [beg len]"
int
db_get_cmd(interp, argc, argv, dbp)
	Tcl_Interp *interp;
	int argc;
	char *argv[];
	DB *dbp;
{
	DBT data, key;
	DB_TXN *txnid;
	Tcl_CmdInfo info;
	db_recno_t ikey;
	u_int32_t flags;
	int arg_off, ret, tclint;

	if (strcmp(argv[1], "bget") == 0) {
		USAGE(argc, 6, DBBGET_USAGE, 0);
		arg_off = 5;
	} else {
		USAGE_GE(argc, 5, DBGET_USAGE, 0);
		arg_off = 4;
	}

	if (Tcl_GetInt(interp, argv[arg_off], &tclint) != TCL_OK) {
		Tcl_AppendResult(interp, "\n", DBGET_USAGE, NULL);
		return (TCL_ERROR);
	}
	flags = (u_int32_t)tclint;

	memset(&key, 0, sizeof(key));
	if (dbp->type == DB_RECNO || strcmp(argv[1], "getn") == 0) {
		if (Tcl_GetInt(interp, argv[3], &tclint) != TCL_OK)
			return (TCL_ERROR);
		ikey = (db_recno_t)tclint;
		key.data = &ikey;
		key.size = sizeof(db_recno_t);
	} else {
		key.data = argv[3];
		key.size = strlen(argv[3]) + 1;	/* Add Null on end */
	}

	memset(&data, 0, sizeof(data));

	/*
	 * If we have "too many" arguments, check for a partial retrieval.
	 */
	if (argc > arg_off + 3 &&
	    strcmp(argv[arg_off + 1], "DB_DBT_PARTIAL") == 0) {
		/* We have a partial */
		USAGE(argc, arg_off + 4, DBGET_USAGE, 0);
		if (Tcl_GetInt(interp, argv[arg_off + 2], &tclint) != TCL_OK)
			return (TCL_ERROR);
		data.doff = (size_t)tclint;
		if (Tcl_GetInt(interp, argv[arg_off + 3], &tclint) != TCL_OK)
			return (TCL_ERROR);
		data.dlen = (size_t)tclint;
		data.flags = DB_DBT_PARTIAL;
		flags = 0;
	}
	if (flags == DB_GET_BOTH) {
		data.data = argv[4];
		data.size = strlen(argv[4]) + 1;
	}

	if (argv[2][0] == '0' && argv[2][1] == '\0')
		txnid = NULL;
	else {
		if (Tcl_GetCommandInfo(interp, argv[2], &info) == 0) {
			Tcl_SetResult(interp,
			    "db_get: Invalid argument ", TCL_STATIC);
			Tcl_AppendResult(interp, argv[2],
			    " not a transaction.", 0);
			return (TCL_ERROR);
		}
		txnid = (DB_TXN *)info.clientData;
	}

	debug_check();

	if (F_ISSET(dbp, DB_AM_THREAD))
		F_SET(&data, DB_DBT_MALLOC);
	if ((ret = dbp->get(dbp, txnid, &key, &data, flags)) == 0) {
		Tcl_ResetResult(interp);
		set_get_result(interp, &data);
		if (F_ISSET(&data, DB_DBT_MALLOC))
			free(data.data);
		return (TCL_OK);
	} else if (ret == DB_NOTFOUND) {
		Tcl_ResetResult(interp);
		Tcl_AppendResult(interp, "Key ", argv[3], " not found.", NULL);
		return (TCL_OK);
	} else {
		Tcl_SetResult(interp, "-1", TCL_STATIC);
		return (TCL_OK);
	}
}

/*
 * Used for getting and verifying the data associated with a binary
 * data field in the database.  These are big and tend to crash tcl.
 * We do the handling of the big stuff in C.  The path is the file
 * into which to write the large key/data element.
 */
#define DBGETBIN_USAGE "dbN getbin path txn key flags"
int
db_getbin_cmd(interp, argc, argv, dbp)
	Tcl_Interp *interp;
	int argc;
	char *argv[];
	DB *dbp;
{
	DBT data, key;
	DB_TXN *txnid;
	Tcl_CmdInfo info;
	db_recno_t rkey;
	u_int32_t flags;
	int ret, tclint;

	USAGE(argc, 6, DBGETBIN_USAGE, 0);

	if (Tcl_GetInt(interp, argv[5], &tclint) != TCL_OK) {
		Tcl_AppendResult(interp, "\n", DBGET_USAGE, NULL);
		return (TCL_ERROR);
	}
	flags = (u_int32_t)tclint;

	memset(&key, 0, sizeof(key));
	if (strcmp(argv[1], "getbinkey") == 0) {
		if (dbt_from_file(interp, argv[4], &key) != TCL_OK)
			return (TCL_ERROR);
	} else if (dbp->type == DB_RECNO) {
		if (Tcl_GetInt(interp, argv[4], &tclint) != TCL_OK)
			return (TCL_ERROR);
		rkey = (db_recno_t)tclint;
		key.data = &rkey;
		key.size = sizeof(db_recno_t);
	} else {
		key.data = argv[4];
		key.size = strlen(argv[4]) + 1;	/* Add Null on end */
	}

	memset(&data, 0, sizeof(data));
	if (strcmp(argv[1], "getbinkey") != 0)
		F_SET(&data, DB_DBT_MALLOC);

	if (argv[3][0] == '0' && argv[3][1] == '\0')
		txnid = NULL;
	else {
		if (Tcl_GetCommandInfo(interp, argv[3], &info) == 0) {
			Tcl_SetResult(interp,
			    "db_getbin: Invalid argument ", TCL_STATIC);
			Tcl_AppendResult(interp, argv[3],
			    " not a transaction.", 0);
			return (TCL_ERROR);
		}
		txnid = (DB_TXN *)info.clientData;
	}

	debug_check();

	if ((ret = dbp->get(dbp, txnid, &key, &data, flags)) == DB_NOTFOUND) {
		Tcl_ResetResult(interp);
		Tcl_AppendResult(interp, "Key ", argv[3], " not found.", NULL);
		return (TCL_OK);
	} else if (ret != 0) {
		Tcl_SetResult(interp, "-1", TCL_STATIC);
		return (TCL_OK);
	}

	/* Got item; write it to file. */
	if (strcmp(argv[1], "getbinkey") == 0) {
		free(key.data);
		Tcl_SetResult(interp, data.data, TCL_VOLATILE);
	} else {
		if (dbt_to_file(interp, argv[2], &data) != TCL_OK)
			return (TCL_ERROR);
		Tcl_SetResult(interp, argv[2], TCL_VOLATILE);
	}

	return (TCL_OK);
}

#define DBPUT_USAGE "dbN put txn key data flags"
#define DBPPUT_USAGE "dbN put txn key data flags doff dlen"
int
db_put_cmd(interp, argc, argv, dbp)
	Tcl_Interp *interp;
	int argc;
	char *argv[];
	DB *dbp;
{
	DBT data, key;
	DB_TXN *txnid;
	Tcl_CmdInfo info;
	db_recno_t ikey;
	u_int32_t flags;
	int ret, tclint;
	char numbuf[16];

	USAGE_GE(argc, 6, DBPUT_USAGE, 0);

	if (Tcl_GetInt(interp, argv[5], &tclint) != TCL_OK) {
		Tcl_AppendResult(interp, "\n", DBPUT_USAGE, NULL);
		return (TCL_ERROR);
	}
	flags = (u_int32_t)tclint;

	memset(&key, 0, sizeof(key));
	if (strcmp(argv[1], "putn") == 0 || dbp->type == DB_RECNO) {
		if (Tcl_GetInt(interp, argv[3], &tclint) != TCL_OK)
			return (TCL_ERROR);
		ikey = (db_recno_t)tclint;
		key.data = &ikey;
		key.size = sizeof(db_recno_t);
	} else {
		key.data = argv[3];
		key.size = strlen(argv[3]) + 1;	/* Add Null on end */
	}

	memset(&data, 0, sizeof(data));
	data.data = argv[4];
	data.size = strlen(argv[4]) + (strcmp(argv[1], "put0") == 0 ? 0 : 1);

	/*
	 * If the partial flag is set, then arguments 6 and 7 are the
	 * offset and length respectively.
	 */
	if (argc > 6 && strcmp(argv[6], "DB_DBT_PARTIAL") == 0) {
		USAGE_GE(argc, 9, DBPPUT_USAGE, 0);
		if (Tcl_GetInt(interp, argv[7], &tclint) != TCL_OK)
			return (TCL_ERROR);
		data.doff = (size_t)tclint;
		if (Tcl_GetInt(interp, argv[8], &tclint) != TCL_OK)
			return (TCL_ERROR);
		data.dlen = (size_t)tclint;
		data.flags = DB_DBT_PARTIAL;
		data.size--;			/* Remove the NULL */
		flags = 0;
	}

	if (argv[2][0] == '0' && argv[2][1] == '\0')
		txnid = NULL;
	else {
		if (Tcl_GetCommandInfo(interp, argv[2], &info) == 0) {
			Tcl_SetResult(interp,
			    "db_put: Invalid argument ", TCL_STATIC);
			Tcl_AppendResult(interp, argv[2],
			    " not a transaction.", 0);
			return (TCL_ERROR);
		}
		txnid = (DB_TXN *)info.clientData;
	}
	debug_check();

	if ((ret = dbp->put(dbp, txnid, &key, &data, flags)) == 0) {
		if (LF_ISSET(DB_APPEND)) {
			if (key.size != sizeof(db_recno_t)) {
				Tcl_SetResult(interp,
				    "Error: key not integer", TCL_STATIC);
				return (TCL_ERROR);
			}
			memcpy(&ikey, key.data, sizeof(db_recno_t));
			snprintf(numbuf, sizeof(numbuf), "%ld", (long)ikey);
			Tcl_SetResult(interp, numbuf, TCL_VOLATILE);
		} else {
			Tcl_SetResult(interp, "0", TCL_STATIC);
		}
	} else if (ret < 0) {
		Tcl_ResetResult(interp);
		Tcl_AppendResult(interp, "Error on put of key ",
		    argv[3], " either not found or bad flag values.", NULL);
	} else {
		Tcl_SetResult(interp, "-1", TCL_STATIC);
	}
	return (TCL_OK);
}

/*
 * Used for putting very large data items into a db file.  Instead of
 * passing the data in a DB, we'll pass a file name and read the data
 * from the file and put it in the db.  This can be checked using the
 * getbin command.
 */
#define DBPUTBIN_USAGE "dbN put txn key data flags"
int
db_putbin_cmd(interp, argc, argv, dbp)
	Tcl_Interp *interp;
	int argc;
	char *argv[];
	DB *dbp;
{
	DBT data, key;
	DB_TXN *txnid;
	Tcl_CmdInfo info;
	db_recno_t rkey;
	u_int32_t flags;
	int ret, tclint;
	void *p;

	USAGE(argc, 6, DBPUTBIN_USAGE, 0);

	if (Tcl_GetInt(interp, argv[5], &tclint) != TCL_OK) {
		Tcl_AppendResult(interp, "\n", DBPUT_USAGE, NULL);
		return (TCL_ERROR);
	}
	flags = (u_int32_t)tclint;

	memset(&key, 0, sizeof(key));
	memset(&data, 0, sizeof(data));
	if (strcmp(argv[1], "putbinkey") == 0) {
		/* Get data from file. */
		if (dbt_from_file(interp, argv[3], &key) != TCL_OK)
			return (TCL_ERROR);
		data.data = argv[4];
		data.size = strlen(argv[4]) + 1;	/* Add Null on end */
		p = key.data;
	} else {
		if (dbt_from_file(interp, argv[4], &data) != TCL_OK)
			return (TCL_ERROR);
		p = data.data;

		if (dbp->type == DB_RECNO) {
			if (Tcl_GetInt(interp, argv[3], &tclint) != TCL_OK)
				return (TCL_ERROR);
			rkey = (db_recno_t)tclint;
			key.data = &rkey;
			key.size = sizeof(db_recno_t);
		} else {
			key.data = argv[3];
			key.size = strlen(argv[3]) + 1;	/* Add Null on end */
		}
	}

	if (argv[2][0] == '0' && argv[2][1] == '\0')
		txnid = NULL;
	else {
		if (Tcl_GetCommandInfo(interp, argv[2], &info) == 0) {
			Tcl_SetResult(interp,
			    "db_putbin: Invalid argument ", TCL_STATIC);
			Tcl_AppendResult(interp, argv[2],
			    " not a transaction.", 0);
			return (TCL_ERROR);
		}
		txnid = (DB_TXN *)info.clientData;
	}
	debug_check();

	ret = dbp->put(dbp, txnid, &key, &data, flags);
	free(p);

	if (ret == 0)
		return (TCL_OK);
	else if (ret < 0) {
		Tcl_ResetResult(interp);
		Tcl_AppendResult(interp, "Key ", argv[3], " not found.", NULL);
		return (TCL_OK);
	} else {
		Tcl_SetResult(interp, "-1", TCL_STATIC);
		return (TCL_OK);
	}
}

/* Cursor support commands. */

#define DBCDEL_USAGE "cursorN del flags"
int
dbc_del_cmd(interp, argc, argv, dbc)
	Tcl_Interp *interp;
	int argc;
	char *argv[];
	DBC *dbc;
{
	u_int32_t flags;
	int ret, tclint;

	USAGE(argc, 3, DBCDEL_USAGE, 0);

	if (Tcl_GetInt(interp, argv[2], &tclint) != TCL_OK) {
		Tcl_AppendResult(interp, "\n", DBCDEL_USAGE, NULL);
		return (TCL_ERROR);
	}
	flags = (u_int32_t)tclint;

	debug_check();

	if ((ret = dbc->c_del(dbc, flags)) == 0)
		return (TCL_OK);
	else if (ret < 0) {
		Tcl_SetResult(interp, "Uninitialized Cursor", TCL_STATIC);
		return (TCL_OK);
	} else {
		Tcl_SetResult(interp, "-1", TCL_STATIC);
		return (TCL_OK);
	}
}

#define DBCGET_USAGE "cursorN get key flags [beg len]"
#define DBCBGET_USAGE "cursorN get key data flags [beg len]"
int
dbc_get_cmd(interp, argc, argv, dbc)
	Tcl_Interp *interp;
	int argc;
	char *argv[];
	DBC *dbc;
{
	DBT data, key;
	db_recno_t rkey;
	u_int32_t flags;
	int arg_off, ret, tclint;
	char numbuf[16];

	if (strcmp(argv[1], "bget") == 0) {
		USAGE(argc, 5, DBCBGET_USAGE, 0);
		arg_off = 4;
	} else {
		USAGE_GE(argc, 4, DBCGET_USAGE, 0);
		arg_off = 3;
	}

	if (Tcl_GetInt(interp, argv[arg_off], &tclint) != TCL_OK) {
		Tcl_AppendResult(interp, "\n", DBCGET_USAGE, NULL);
		return (TCL_ERROR);
	}
	flags = (u_int32_t)tclint;

	memset(&key, 0, sizeof(key));
	if (dbc->dbp->type == DB_RECNO || strcmp(argv[1], "getn") == 0) {
		if (Tcl_GetInt(interp, argv[2], &tclint) != TCL_OK)
			return (TCL_ERROR);
		rkey = (db_recno_t)tclint;
		key.data = &rkey;
		key.size = sizeof(db_recno_t);
	} else {
		key.data = argv[2];
		key.size = strlen(argv[2]) + 1;	/* Add Null on end */
	}

	memset(&data, 0, sizeof(data));
	if (argc > arg_off + 3 &&
	    strcmp(argv[arg_off + 1], "DB_DBT_PARTIAL") == 0) {
		/* We have a partial */
		USAGE(argc, arg_off + 4, DBGET_USAGE, 0);
		if (Tcl_GetInt(interp, argv[arg_off + 2], &tclint) != TCL_OK)
			return (TCL_ERROR);
		data.doff = (size_t)tclint;
		if (Tcl_GetInt(interp, argv[arg_off + 3], &tclint) != TCL_OK)
			return (TCL_ERROR);
		data.dlen = (size_t)tclint;
		data.flags = DB_DBT_PARTIAL;
		flags = 0;
	}

	if (flags == DB_GET_BOTH) {
		data.data = argv[3];
		data.size = strlen(argv[3]) + 1;
	}

	debug_check();

	if (F_ISSET(dbc->dbp, DB_AM_THREAD)) {
		F_SET(&data, DB_DBT_MALLOC);
		switch(flags) {
		case DB_FIRST:
		case DB_LAST:
		case DB_NEXT:
		case DB_PREV:
		case DB_CURRENT:
		case DB_SET_RANGE:
		case DB_SET_RECNO:
		case DB_GET_RECNO:
			F_SET(&key, DB_DBT_MALLOC);
			break;
		/* case DB_SET: Do nothing */
		/* case DB_GET_BOTH: Do nothing */
		}
	}
	if ((ret = dbc->c_get(dbc, &key, &data, flags)) == 0) {
		if (dbc->dbp->type == DB_RECNO) {
			if (key.size != sizeof(db_recno_t)) {
				Tcl_SetResult(interp,
				    "Error: key not integer", TCL_STATIC);
				if (F_ISSET(&data, DB_DBT_MALLOC))
					free(&data.data);
				if (F_ISSET(&key, DB_DBT_MALLOC))
					free(&key.data);
				return (TCL_ERROR);
			}
			memcpy(&rkey, key.data, sizeof(db_recno_t));
			snprintf(numbuf, sizeof(numbuf), "%ld", (long)rkey);
			Tcl_SetResult(interp, numbuf, TCL_VOLATILE);
		} else
			Tcl_SetResult(interp, key.data, TCL_VOLATILE);

		if ((flags & DB_OPFLAGS_MASK) != DB_JOIN_ITEM) {
			Tcl_AppendResult(interp, " {", NULL);
			set_get_result(interp, &data);
			Tcl_AppendResult(interp, "} ", NULL);
		}
		if (F_ISSET(&data, DB_DBT_MALLOC))
			free(&data.data);
		if (F_ISSET(&key, DB_DBT_MALLOC))
			free(&key.data);
		return (TCL_OK);
	} else if (ret < 0) {	/* End/Beginning of file. */
		Tcl_ResetResult(interp);
		return (TCL_OK);
	} else {
		Tcl_SetResult(interp, "-1", TCL_STATIC);
		return (TCL_OK);
	}
}

#define DBCGETBIN_USAGE "cursorN getbin file key flags"
int
dbc_getbin_cmd(interp, argc, argv, dbc)
	Tcl_Interp *interp;
	int argc;
	char *argv[];
	DBC *dbc;
{
	DBT data, key, *dbt;
	db_recno_t rkey;
	u_int32_t flags;
	int ret, tclint;
	char nbuf[32];

	USAGE(argc, 5, DBCGET_USAGE, 0);

	if (Tcl_GetInt(interp, argv[4], &tclint) != TCL_OK) {
		Tcl_AppendResult(interp, "\n", DBCGET_USAGE, NULL);
		return (TCL_ERROR);
	}
	flags = (u_int32_t)tclint;

	memset(&key, 0, sizeof(key));
	if (flags == DB_SET || flags == DB_KEYFIRST || flags == DB_KEYLAST)
		if (strcmp(argv[1], "getbinkey") == 0) {
			if (dbt_from_file(interp, argv[3], &key) != TCL_OK)
				return (TCL_ERROR);
		} else if (dbc->dbp->type == DB_RECNO) {
			if (Tcl_GetInt(interp, argv[3], &tclint) != TCL_OK)
				return (TCL_ERROR);
			rkey = (db_recno_t)tclint;
			key.data = &rkey;
			key.size = sizeof(db_recno_t);
		} else {
			key.data = argv[3];
			key.size = strlen(argv[3]) + 1;	/* Add Null on end */
		}

	memset(&data, 0, sizeof(data));
	if (strcmp(argv[1], "getbinkey") != 0 && dbc->dbp->type != DB_RECNO)
		F_SET(&data, DB_DBT_MALLOC);

	debug_check();

	ret = dbc->c_get(dbc, &key, &data, flags);
	if (ret < 0) {	/* End/Beginning of file. */
		Tcl_ResetResult(interp);
		return (TCL_OK);
	} else if (ret != 0) {
		Tcl_SetResult(interp, "-1", TCL_STATIC);
		return (TCL_OK);
	}

	/* Got key; write it or its data to a file. */
	if (strcmp(argv[1], "getbinkey") == 0) {
		dbt = &key;
		Tcl_SetResult(interp, data.data, TCL_VOLATILE);
	} else if (dbc->dbp->type == DB_RECNO) {
		dbt = &data;
		memcpy(&rkey, key.data, sizeof(db_recno_t));
		snprintf(nbuf, sizeof(nbuf), "%ld", (long)rkey);
		Tcl_SetResult(interp, nbuf, TCL_VOLATILE);
	} else {
		dbt = &data;
		Tcl_SetResult(interp, key.data, TCL_VOLATILE);
	}

	if (dbt_to_file(interp, argv[2], dbt) != TCL_OK)
		return (TCL_ERROR);

	Tcl_AppendElement(interp, argv[2]);
	return (TCL_OK);
}

#define DBCPUT_USAGE "cursorN put key data flags"
int
dbc_put_cmd(interp, argc, argv, dbc)
	Tcl_Interp *interp;
	int argc;
	char *argv[];
	DBC *dbc;
{
	DBT data, key;
	db_recno_t ikey;
	u_int32_t flags;
	int ret, tclint;

	USAGE(argc, 5, DBCPUT_USAGE, 0);

	if (Tcl_GetInt(interp, argv[4], &tclint) != TCL_OK) {
		Tcl_AppendResult(interp, "\n", DBCPUT_USAGE, NULL);
		return (TCL_ERROR);
	}
	flags = (u_int32_t)tclint;

	/*
	 * If this is a DB_CURRENT, we should never look at the key, so
	 * send it a NULL.
	 */
	memset(&key, 0, sizeof(key));
	if (flags != DB_CURRENT)
		if (strcmp(argv[1], "putn") == 0 ||
		    dbc->dbp->type == DB_RECNO) {
			if (Tcl_GetInt(interp, argv[2], &tclint) != TCL_OK)
				return (TCL_ERROR);
			ikey = (db_recno_t)tclint;
			key.data = &ikey;
			key.size = sizeof(db_recno_t);
		} else {
			key.data = argv[2];
			key.size = strlen(argv[2]) + 1;	/* Add Null on end */
		}

	memset(&data, 0, sizeof(data));
	data.data = argv[3];
	data.size = strlen(argv[3]) + 1;

	debug_check();

	if ((ret = dbc->c_put(dbc, &key, &data, flags)) == 0)
		return (TCL_OK);
	else if (ret < 0) {
		Tcl_ResetResult(interp);
		Tcl_AppendResult(interp, "Key ", argv[2], " not found.", NULL);
		return (TCL_OK);
	} else {
		Tcl_SetResult(interp, "-1", TCL_STATIC);
		return (TCL_OK);
	}
}

u_int8_t *
list_to_numarray(interp, str)
	Tcl_Interp *interp;
	char *str;
{
	int argc, i, tmp;
	u_int8_t *nums, *np;
	char **argv, **ap;

	if (Tcl_SplitList(interp, str, &argc, &argv) != TCL_OK)
		return (NULL);
	if ((nums = (u_int8_t *)calloc(argc, sizeof(u_int8_t))) == NULL) {
		Tcl_SetResult(interp, Tcl_PosixError(interp), TCL_STATIC);
		return (NULL);
	}

	for (np = nums, ap = argv, i = 0; i < argc; i++, ap++, np++) {
		if (Tcl_GetInt(interp, *ap, &tmp) != TCL_OK) {
			free (nums);
			return (NULL);
		}
		*np = (u_int8_t)tmp;		/* XXX: Possible overflow. */
	}
	FREE_TCL(argv);
	return (nums);
}

#define STAMP_USAGE "timestamp [-r]"
int
stamp_cmd(notused, interp, argc, argv)
	ClientData notused;
	Tcl_Interp *interp;
	int argc;
	char *argv[];
{
	static time_t start;
	struct tm *tp;
	time_t elapsed, now;
	static char buf[25];

	notused = NULL;

	USAGE_GE(argc, 1, STAMP_USAGE, 0);
	(void)time(&now);
	if (now == (time_t)-1)
		goto err;
	if (argc > 1 && strcmp(argv[1], "-r") == 0) {
		snprintf(buf, sizeof(buf), "%lu", (u_long)now);
		Tcl_SetResult(interp, buf, TCL_STATIC);
		return (TCL_OK);
	}
	if ((tp = localtime(&now)) == NULL)
		goto err;

	if (start == 0)
		start = now;
	elapsed = now - start;
	snprintf(buf, sizeof(buf), "%02d:%02d:%02d (%02d:%02d:%02d)",
	    tp->tm_hour, tp->tm_min, tp->tm_sec, (int)(elapsed / 3600),
	    (int)((elapsed % 3600) / 60), (int)(((elapsed % 3600) % 60)));
	start = now;

	Tcl_SetResult(interp, buf, TCL_STATIC);
	return (TCL_OK);

err:	Tcl_SetResult(interp, Tcl_PosixError(interp), TCL_STATIC);
	return (TCL_ERROR);
}

int debug_stop;				/* Stop on each iteration. */

void
debug_check()
{
	if (debug_on == 0)
		return;

	if (debug_print != 0) {
		printf("\r%6d:", debug_on);
		fflush(stdout);
	}

	if (debug_on++ == debug_test || debug_stop)
		__db_loadme();
}

int
dbt_from_file(interp, file, dbt)
	Tcl_Interp *interp;
	char *file;
	DBT *dbt;
{
	size_t len, size;
	ssize_t nr;
	u_int32_t mbytes, bytes;
	int fd;

	/* Get data from file. */
	fd = -1;
	len = strlen(file) + 1;
	if ((errno = __db_open(file, DB_RDONLY, DB_RDONLY, 0, &fd)) != 0)
		goto err;
	if ((errno = __os_ioinfo(file, fd, &mbytes, &bytes, NULL)) != 0)
		goto err;
	size = mbytes * MEGABYTE + bytes;
	if ((dbt->data = (void *)malloc(size + len)) == NULL)
		goto err;
	if ((errno =
	    __os_read(fd, ((u_int8_t *)dbt->data) + len, size, &nr)) != 0)
		goto err;
	if (nr != (ssize_t)size) {
		errno = EIO;
		goto err;
	}
	if ((errno =__os_close(fd)) != 0) {
err:		if (fd != -1)
			close(fd);
		Tcl_SetResult(interp, Tcl_PosixError(interp), TCL_STATIC);
		return (TCL_ERROR);
	}
	strcpy((char *)(dbt->data), file);
	dbt->size = size + (u_int32_t)len;
	dbt->flags = 0;
	return (TCL_OK);
}

/*
 * file is the file we are creating.  ofile is the original file
 * name so we know how many characters to drop off the end.
 */
int
dbt_to_file(interp, file, dbt)
	Tcl_Interp *interp;
	char *file;
	DBT *dbt;
{
	size_t len;
	ssize_t nw;
	int fd;
	char *p;

	/* Got key; write it to file. */
	for (len = 1, p = (char *)(dbt->data); *p != '\0'; len++, p++)
		;
	p++;

	fd = -1;
	if ((errno = __db_open(file, DB_CREATE | DB_TRUNCATE,
	    DB_CREATE | DB_TRUNCATE, 0644, &fd)) != 0 ||
	    (errno = __os_write(fd, p, dbt->size - len, &nw) != 0) ||
	    nw != (ssize_t)(dbt->size - len) ||
	    (errno = __os_close(fd)) != 0) {
		if (errno == 0)
			errno = EIO;
		if (fd == -1)
			(void)__os_close(fd);
		Tcl_SetResult(interp, Tcl_PosixError(interp), TCL_STATIC);
		return (TCL_ERROR);
	}
	if (F_ISSET(dbt, DB_DBT_MALLOC))
		free(dbt->data);
	return (TCL_OK);
}

void
set_get_result(interp, dbt)
	Tcl_Interp *interp;
	DBT *dbt;
{
	size_t i, len;
	u_int8_t *p;
	char numbuf[32], sprbuf[128], *outbuf;

	for (i = 0, p = dbt->data; i < dbt->size && *p == '\0'; i++, p++)
		;
	/*
	 * If this is a partial get, we need to make sure that the last
	 * character is a NUL so that tcl can handle it.
	 */
	if (F_ISSET(dbt, DB_DBT_PARTIAL)) {
		if (dbt->size == 0)
			((char *)dbt->data)[0] = '\0';
		else
			((char *)dbt->data)[dbt->size - 1] = '\0';
	}

	if (i == 0) {
		if (((u_int8_t *)dbt->data)[dbt->size - 1] != '\0') {
			outbuf = (char *)malloc(dbt->size + 1);
			memcpy(outbuf, dbt->data, dbt->size);
			outbuf[dbt->size] = '\0';
		} else
			outbuf = dbt->data;
		Tcl_AppendResult(interp, outbuf, NULL);
		if (outbuf != dbt->data)
			free(outbuf);
	} else {
		snprintf(numbuf, sizeof(numbuf), "%lu", (u_long)i);
		snprintf(sprbuf, sizeof(sprbuf), " %%.%ds", dbt->size - i);
		len = dbt->size - i + 5;
		if ((outbuf = (char *)malloc(len)) != NULL) {
			snprintf(outbuf, len, sprbuf, p);
			Tcl_AppendResult(interp, numbuf, outbuf, NULL);
			free(outbuf);
		}
	}
}

#define SRAND_USAGE "srand seed"
int
srand_cmd(notused, interp, argc, argv)
	ClientData notused;
	Tcl_Interp *interp;
	int argc;
	char *argv[];
{
	int ival;

	notused = NULL;
	USAGE(argc, 2, SRAND_USAGE, 0);
	if (Tcl_GetInt(interp, argv[1], &ival) != TCL_OK)
		return (TCL_ERROR);

	srand((u_int)ival);
	Tcl_SetResult(interp, "0", TCL_STATIC);
	return (TCL_OK);
}

#define RAND_USAGE "rand"
int
rand_cmd(notused, interp, argc, argv)
	ClientData notused;
	Tcl_Interp *interp;
	int argc;
	char *argv[];
{
	char retbuf[50];

	notused = NULL;
	argv = NULL;
	USAGE(argc, 1, RAND_USAGE, 0);

	snprintf(retbuf, sizeof(retbuf), "%ld", (long)rand());
	Tcl_SetResult(interp, retbuf, TCL_VOLATILE);
	return (TCL_OK);
}

#define RANDOMINT_USAGE "random_int lo hi"
int
randomint_cmd(notused, interp, argc, argv)
	ClientData notused;
	Tcl_Interp *interp;
	int argc;
	char *argv[];
{
	long t;
	int lo, hi, ret;
	char retbuf[50];

	notused = NULL;
	USAGE(argc, 3, RANDOMINT_USAGE, 0);
	if (Tcl_GetInt(interp, argv[1], &lo) != TCL_OK ||
	    Tcl_GetInt(interp, argv[2], &hi) != TCL_OK)
		return (TCL_ERROR);

#ifndef	RAND_MAX
#define	RAND_MAX	0x7fffffff
#endif
	t = rand();
	if (t > RAND_MAX)
		printf("Max random is higher than %ld\n", (long)RAND_MAX);
	ret = (int)(((double)t / ((double)(RAND_MAX) + 1)) * (hi - lo + 1));
	ret += lo;
	snprintf(retbuf, sizeof(retbuf), "%d", ret);
	Tcl_SetResult(interp, retbuf, TCL_VOLATILE);
	return (TCL_OK);
}

#define VERSION_USAGE "db_version"
int
dbversion_cmd(notused, interp, argc, notused2)
	ClientData notused;
	Tcl_Interp *interp;
	int argc;
	char *notused2[];
{
	char *str;

	notused = NULL;
	notused2 = NULL;
	USAGE(argc, 1, VERSION_USAGE, 0);
	str = db_version(NULL, NULL, NULL);
	if (str == NULL) {
		Tcl_SetResult(interp, "db_version: ", TCL_STATIC);
		Tcl_AppendResult(interp, Tcl_PosixError(interp), 0);
		return (TCL_ERROR);
	}
	Tcl_SetResult(interp, str, TCL_DYNAMIC);
	return (TCL_OK);
}

/*
 * Create an environment that we can pass around between different
 * calls.
 */
#define DBENV_USAGE "dbenv "
int
dbenv_cmd(notused, interp, argc, argv)
	ClientData notused;
	Tcl_Interp *interp;
	int argc;
	char *argv[];
{
	static int env_number = 0;
	DB_ENV *env;
	char envname[50];

	debug_check();
	notused = NULL;

	/* Check number of arguments. */
	USAGE_GE(argc, 1, DBENV_USAGE, DO_ENV);

	process_env_options(interp, argc - 1, &argv[1], &env);
	if (env == NULL) {
		Tcl_SetResult(interp, "NULL", TCL_STATIC);
		return (TCL_OK);
	}

	F_SET(env, DB_ENV_STANDALONE);

	/* Create new command name. */
	snprintf(envname, sizeof(envname), "env%d", env_number);
	env_number++;

	Tcl_CreateCommand(interp, envname, envwidget_cmd, (ClientData)env,
	    envwidget_delcmd);
	Tcl_SetResult(interp, envname, TCL_VOLATILE);
	return (TCL_OK);
}

void
envwidget_delcmd(cd)
	ClientData cd;
{
	DB_ENV *env;
	u_int8_t *conflicts;

	debug_check();
	env = (DB_ENV *)cd;

	if (!F_ISSET(env, DB_ENV_CDB) && env->lk_conflicts != NULL) {
		conflicts = (u_int8_t *)env->lk_conflicts;
		free(conflicts);
	}
	(void)db_appexit(env);
	free(env);
}

#define	ENVWIDGET_USAGE "envN"
#define	ENVWIDGET_SIMPLEDUP "envN simpledup"
int
envwidget_cmd(cd, interp, argc, argv)
	ClientData cd;
	Tcl_Interp *interp;
	int argc;
	char *argv[];
{
	static int nenv_number = 0;
	DB_ENV *env, *newenv;
	char **p, nenvname[50];

	argv = argv;
	env = (DB_ENV *)cd;

	if (argc == 1) {
		USAGE(argc, 1, ENVWIDGET_USAGE, 0);

		Tcl_ResetResult(interp);
		if (env->db_home != NULL)
			Tcl_AppendResult(interp, " Home: ", env->db_home, 0);
		if ((p = env->db_data_dir) != NULL)
			for (; *p != NULL; ++p)
				Tcl_AppendResult(interp, " Data: ", *p, 0);
		if (env->db_log_dir != NULL)
			Tcl_AppendResult(interp, " Log: ", env->db_log_dir, 0);
		if (env->db_tmp_dir != NULL)
			Tcl_AppendResult(interp, " Tmp: ", env->db_tmp_dir, 0);
	} else if (strcmp(argv[1], "simpledup") == 0) {
		USAGE(argc, 2, ENVWIDGET_SIMPLEDUP, 0);
		/*
		 * Copy the env and then NULL out the log, lock and
		 * transaction info pointers so that we only share an
		 * mpool.
		 */
		newenv = (DB_ENV *)malloc(sizeof(*newenv));
		*newenv = *env;
		newenv->lg_info = NULL;
		newenv->lk_info = NULL;
		newenv->tx_info = NULL;

		/* Create new command name. */
		snprintf(nenvname, sizeof(nenvname), "nenv%d", nenv_number);
		nenv_number++;

		Tcl_CreateCommand(interp, nenvname, envwidget_cmd,
		    (ClientData)newenv, NULL);
		Tcl_SetResult(interp, nenvname, TCL_VOLATILE);
	} else {
		Tcl_SetResult(interp, "Invalid command", TCL_STATIC);
		return (TCL_ERROR);
	}

	return (TCL_OK);
}

#define ARGS_USAGE "args"
int
args_cmd(cd, interp, argc, argv)
	ClientData cd;
	Tcl_Interp *interp;
	int argc;
	char *argv[];
{
	cd = cd;
	argv = argv;

	USAGE(argc, 1, ARGS_USAGE, 0);
	printf("Legal environment options are: %s\n", DB_ENV_FLAGS);
	printf("Legal access method options are: %s\n", DB_INFO_FLAGS);
	Tcl_ResetResult(interp);
	return (TCL_OK);
}

#define DEBUG_CHECK_USAGE "debug_check"
int
debugcheck_cmd(notused1, interp, argc, notused2)
	ClientData notused1;
	Tcl_Interp *interp;
	int argc;
	char *notused2[];
{
	USAGE(argc, 1, DEBUG_CHECK_USAGE, 0);
	notused1 = notused1;
	notused2 = notused2;
	debug_check();
	Tcl_ResetResult(interp);
	return (TCL_OK);
}

#define DB_JOIN_USAGE "dbN join {DBC ... DBC} flags"
int
db_join_cmd(interp, argc, argv, dbp)
	Tcl_Interp *interp;
	int argc;
	char *argv[];
	DB *dbp;
{
	static int jcurs_id = 0;
	int i, tclint, ncurs, ret;
	u_int32_t flags;
	char **cursnames;
	char cursname[128];
	Tcl_CmdInfo info;
	DBC *dbc, **curs_array;

	USAGE(argc, 4, DB_JOIN_USAGE, 0);

	if (Tcl_GetInt(interp, argv[3], &tclint) != TCL_OK) {
		Tcl_SetResult(interp, "\nUsage: ", TCL_STATIC);
		Tcl_AppendResult(interp, DB_JOIN_USAGE, NULL);
		return (TCL_OK);
	}

	flags = (u_int32_t)tclint;

	/* Now, split the cursor list into an array. */
	if (Tcl_SplitList(interp, argv[2], &ncurs, &cursnames) != TCL_OK) {
		Tcl_SetResult(interp, "dbjoin: Invalid list ", TCL_STATIC);
		Tcl_AppendResult(interp, argv[2], NULL);
		return (TCL_OK);
	}

	/* Malloc the array to pass to join. */
	curs_array = (DBC **)malloc(sizeof(DBC *) * (ncurs + 1));
	if (curs_array == NULL) {
		Tcl_SetResult(interp, "dbjoin: ", TCL_STATIC);
		Tcl_AppendResult(interp, Tcl_PosixError(interp), NULL);
		goto end1;
	}
	for (i = 0; i < ncurs; i++) {
		if (Tcl_GetCommandInfo(interp, cursnames[i], &info) == 0) {
			Tcl_SetResult(interp,
			    "dbjoin: Invalid cursor ", TCL_STATIC);
			Tcl_AppendResult(interp, cursnames[i], NULL);
			goto end2;
		}
		curs_array[i] = (DBC *)info.clientData;
	}
	curs_array[i] = NULL;

	if ((ret = dbp->join(dbp, curs_array, 0, &dbc)) == 0) {
		snprintf(cursname, sizeof(cursname), "join.cursor%d", jcurs_id);
		jcurs_id++;
		Tcl_CreateCommand(interp, cursname, dbcursor_cmd,
		    (ClientData)dbc, NULL);
		Tcl_SetResult(interp, cursname, TCL_VOLATILE);
	} else {
		Tcl_SetResult(interp, "db_cursor:", TCL_STATIC);
		errno = ret;
		Tcl_AppendResult(interp, Tcl_PosixError(interp), 0);
	}

end2:	free(curs_array);
end1:	FREE_TCL(cursnames);
	return (TCL_OK);
}
