/*-
 * See the file LICENSE for redistribution information.
 *
 * Copyright (c) 1999,2008 Oracle.  All rights reserved.
 *
 * $Id: tcl_rep.c,v 12.53 2008/02/05 13:00:22 sue Exp $
 */

#include "db_config.h"

#include "db_int.h"
#ifdef HAVE_SYSTEM_INCLUDE_FILES
#include <tcl.h>
#endif
#include "dbinc/tcl_db.h"

#ifdef CONFIG_TEST
/*
 * tcl_RepConfig --
 *	Call DB_ENV->rep_set_config().
 *
 * PUBLIC: int tcl_RepConfig
 * PUBLIC:     __P((Tcl_Interp *, DB_ENV *, Tcl_Obj *));
 */
int
tcl_RepConfig(interp, dbenv, list)
	Tcl_Interp *interp;		/* Interpreter */
	DB_ENV *dbenv;			/* Environment pointer */
	Tcl_Obj *list;			/* {which on|off} */
{
	static const char *confwhich[] = {
		"bulk",
		"delayclient",
		"noautoinit",
		"nowait",
		NULL
	};
	enum confwhich {
		REPCONF_BULK,
		REPCONF_DELAYCLIENT,
		REPCONF_NOAUTOINIT,
		REPCONF_NOWAIT
	};
	static const char *confonoff[] = {
		"off",
		"on",
		NULL
	};
	enum confonoff {
		REPCONF_OFF,
		REPCONF_ON
	};
	Tcl_Obj **myobjv, *onoff, *which;
	int myobjc, on, optindex, result, ret;
	u_int32_t wh;

	result = Tcl_ListObjGetElements(interp, list, &myobjc, &myobjv);
	which = myobjv[0];
	onoff = myobjv[1];
	if (result != TCL_OK)
		return (result);
	if (Tcl_GetIndexFromObj(interp, which, confwhich, "option",
	    TCL_EXACT, &optindex) != TCL_OK)
		return (IS_HELP(which));

	switch ((enum confwhich)optindex) {
	case REPCONF_NOAUTOINIT:
		wh = DB_REP_CONF_NOAUTOINIT;
		break;
	case REPCONF_BULK:
		wh = DB_REP_CONF_BULK;
		break;
	case REPCONF_DELAYCLIENT:
		wh = DB_REP_CONF_DELAYCLIENT;
		break;
	case REPCONF_NOWAIT:
		wh = DB_REP_CONF_NOWAIT;
		break;
	default:
		return (TCL_ERROR);
	}
	if (Tcl_GetIndexFromObj(interp, onoff, confonoff, "option",
	    TCL_EXACT, &optindex) != TCL_OK)
		return (IS_HELP(onoff));
	switch ((enum confonoff)optindex) {
	case REPCONF_OFF:
		on = 0;
		break;
	case REPCONF_ON:
		on = 1;
		break;
	default:
		return (TCL_ERROR);
	}
	ret = dbenv->rep_set_config(dbenv, wh, on);
	return (_ReturnSetup(interp, ret, DB_RETOK_STD(ret),
	    "env rep_config"));
}

/*
 * tcl_RepGetTwo --
 *	Call replication getters that return 2 values.
 *
 * PUBLIC: int tcl_RepGetTwo
 * PUBLIC:     __P((Tcl_Interp *, DB_ENV *, int));
 */
int
tcl_RepGetTwo(interp, dbenv, op)
	Tcl_Interp *interp;		/* Interpreter */
	DB_ENV *dbenv;			/* Environment pointer */
	int op;				/* which getter */
{
	Tcl_Obj *myobjv[2], *res;
	u_int32_t val1, val2;
	int myobjc, result, ret;

	ret = 0;
	val1 = val2 = 0;
	switch (op) {
	case DBTCL_GETCLOCK:
		ret = dbenv->rep_get_clockskew(dbenv, &val1, &val2);
		break;
	case DBTCL_GETLIMIT:
		ret = dbenv->rep_get_limit(dbenv, &val1, &val2);
		break;
	case DBTCL_GETREQ:
		ret = dbenv->rep_get_request(dbenv, &val1, &val2);
		break;
	default:
		return (TCL_ERROR);
	}
	if ((result = _ReturnSetup(interp, ret, DB_RETOK_STD(ret),
	    "env rep_get")) == TCL_OK) {
		myobjc = 2;
		myobjv[0] = Tcl_NewLongObj((long)val1);
		myobjv[1] = Tcl_NewLongObj((long)val2);
		res = Tcl_NewListObj(myobjc, myobjv);
		Tcl_SetObjResult(interp, res);
	}
	return (result);
}

/*
 * tcl_RepGetConfig --
 *
 * PUBLIC: int tcl_RepGetConfig
 * PUBLIC:     __P((Tcl_Interp *, DB_ENV *, Tcl_Obj *));
 */
int
tcl_RepGetConfig(interp, dbenv, which)
	Tcl_Interp *interp;		/* Interpreter */
	DB_ENV *dbenv;			/* Environment pointer */
	Tcl_Obj *which;			/* which flag */
{
	static const char *confwhich[] = {
		"bulk",
		"delayclient",
		"lease",
		"noautoinit",
		"nowait",
		NULL
	};
	enum confwhich {
		REPGCONF_BULK,
		REPGCONF_DELAYCLIENT,
		REPGCONF_LEASE,
		REPGCONF_NOAUTOINIT,
		REPGCONF_NOWAIT
	};
	Tcl_Obj *res;
	int on, optindex, result, ret;
	u_int32_t wh;

	if (Tcl_GetIndexFromObj(interp, which, confwhich, "option",
	    TCL_EXACT, &optindex) != TCL_OK)
		return (IS_HELP(which));

	res = NULL;
	switch ((enum confwhich)optindex) {
	case REPGCONF_BULK:
		wh = DB_REP_CONF_BULK;
		break;
	case REPGCONF_DELAYCLIENT:
		wh = DB_REP_CONF_DELAYCLIENT;
		break;
	case REPGCONF_LEASE:
		wh = DB_REP_CONF_LEASE;
		break;
	case REPGCONF_NOAUTOINIT:
		wh = DB_REP_CONF_NOAUTOINIT;
		break;
	case REPGCONF_NOWAIT:
		wh = DB_REP_CONF_NOWAIT;
		break;
	default:
		return (TCL_ERROR);
	}
	ret = dbenv->rep_get_config(dbenv, wh, &on);
	if ((result = _ReturnSetup(interp, ret, DB_RETOK_STD(ret),
	    "env rep_config")) == TCL_OK) {
		res = Tcl_NewIntObj(on);
		Tcl_SetObjResult(interp, res);
	}
	return (result);
}

/*
 * tcl_RepGetTimeout --
 *	Get various replication timeout values.
 *
 * PUBLIC: int tcl_RepGetTimeout
 * PUBLIC:     __P((Tcl_Interp *, DB_ENV *, Tcl_Obj *));
 */
int
tcl_RepGetTimeout(interp, dbenv, which)
	Tcl_Interp *interp;		/* Interpreter */
	DB_ENV *dbenv;			/* Environment pointer */
	Tcl_Obj *which;			/* which flag */
{
	static const char *towhich[] = {
		"ack",
		"checkpoint_delay",
		"connection_retry",
		"election",
		"election_retry",
		"full_election",
		"heartbeat_monitor",
		"heartbeat_send",
		"lease",
		NULL
	};
	enum towhich {
		REPGTO_ACK,
		REPGTO_CKP,
		REPGTO_CONN,
		REPGTO_ELECT,
		REPGTO_ELECT_RETRY,
		REPGTO_FULL,
		REPGTO_HB_MON,
		REPGTO_HB_SEND,
		REPGTO_LEASE
	};
	Tcl_Obj *res;
	int optindex, result, ret, wh;
	u_int32_t to;

	if (Tcl_GetIndexFromObj(interp, which, towhich, "option",
	    TCL_EXACT, &optindex) != TCL_OK)
		return (IS_HELP(which));

	res = NULL;
	switch ((enum towhich)optindex) {
	case REPGTO_ACK:
		wh = DB_REP_ACK_TIMEOUT;
		break;
	case REPGTO_CKP:
		wh = DB_REP_CHECKPOINT_DELAY;
		break;
	case REPGTO_CONN:
		wh = DB_REP_CONNECTION_RETRY;
		break;
	case REPGTO_ELECT:
		wh = DB_REP_ELECTION_TIMEOUT;
		break;
	case REPGTO_ELECT_RETRY:
		wh = DB_REP_ELECTION_RETRY;
		break;
	case REPGTO_FULL:
		wh = DB_REP_FULL_ELECTION_TIMEOUT;
		break;
	case REPGTO_HB_MON:
		wh = DB_REP_HEARTBEAT_MONITOR;
		break;
	case REPGTO_HB_SEND:
		wh = DB_REP_HEARTBEAT_SEND;
		break;
	case REPGTO_LEASE:
		wh = DB_REP_LEASE_TIMEOUT;
		break;
	default:
		return (TCL_ERROR);
	}
	ret = dbenv->rep_get_timeout(dbenv, wh, &to);
	if ((result = _ReturnSetup(interp, ret, DB_RETOK_STD(ret),
	    "env rep_config")) == TCL_OK) {
		res = Tcl_NewLongObj((long)to);
		Tcl_SetObjResult(interp, res);
	}
	return (result);
}
#endif

#ifdef CONFIG_TEST
/*
 * tcl_RepElect --
 *	Call DB_ENV->rep_elect().
 *
 * PUBLIC: int tcl_RepElect
 * PUBLIC:     __P((Tcl_Interp *, int, Tcl_Obj * CONST *, DB_ENV *));
 */
int
tcl_RepElect(interp, objc, objv, dbenv)
	Tcl_Interp *interp;		/* Interpreter */
	int objc;			/* How many arguments? */
	Tcl_Obj *CONST objv[];		/* The argument objects */
	DB_ENV *dbenv;			/* Environment pointer */
{
	int result, ret;
	u_int32_t full_timeout, nsites, nvotes, pri, timeout;

	if (objc != 6 && objc != 7) {
		Tcl_WrongNumArgs(interp, 6, objv,
		    "nsites nvotes pri timeout [full_timeout]");
		return (TCL_ERROR);
	}

	if ((result = _GetUInt32(interp, objv[2], &nsites)) != TCL_OK)
		return (result);
	if ((result = _GetUInt32(interp, objv[3], &nvotes)) != TCL_OK)
		return (result);
	if ((result = _GetUInt32(interp, objv[4], &pri)) != TCL_OK)
		return (result);
	if ((result = _GetUInt32(interp, objv[5], &timeout)) != TCL_OK)
		return (result);
	full_timeout = 0;
	if (objc == 7)
		if ((result = _GetUInt32(interp, objv[6], &full_timeout))
		    != TCL_OK)
			return (result);

	_debug_check();

	if ((ret = dbenv->rep_set_priority(dbenv, pri)) != 0)
		return (_ReturnSetup(interp, ret, DB_RETOK_STD(ret),
			    "env rep_elect (rep_set_priority)"));
	if ((ret = dbenv->rep_set_timeout(dbenv, DB_REP_ELECTION_TIMEOUT,
	    timeout)) != 0)
		return (_ReturnSetup(interp, ret, DB_RETOK_STD(ret),
			    "env rep_elect (rep_set_timeout)"));

	if (full_timeout != 0 && (ret = dbenv->rep_set_timeout(dbenv,
	    DB_REP_FULL_ELECTION_TIMEOUT, full_timeout)) != 0)
		return (_ReturnSetup(interp, ret, DB_RETOK_STD(ret),
			    "env rep_elect (rep_set_timeout)"));

	ret = dbenv->rep_elect(dbenv, nsites, nvotes, 0);
	return (_ReturnSetup(interp, ret, DB_RETOK_STD(ret), "env rep_elect"));
}
#endif

#ifdef CONFIG_TEST
/*
 * tcl_RepFlush --
 *	Call DB_ENV->rep_flush().
 *
 * PUBLIC: int tcl_RepFlush
 * PUBLIC:     __P((Tcl_Interp *, int, Tcl_Obj * CONST *, DB_ENV *));
 */
int
tcl_RepFlush(interp, objc, objv, dbenv)
	Tcl_Interp *interp;
	int objc;
	Tcl_Obj *CONST objv[];
	DB_ENV *dbenv;
{
	int ret;

	if (objc != 2) {
		Tcl_WrongNumArgs(interp, 2, objv, "");
		return TCL_ERROR;
	}

	_debug_check();
	ret = dbenv->rep_flush(dbenv);
	return (_ReturnSetup(interp, ret, DB_RETOK_STD(ret), "env rep_flush"));
}
#endif

#ifdef CONFIG_TEST
/*
 * tcl_RepSync --
 *	Call DB_ENV->rep_sync().
 *
 * PUBLIC: int tcl_RepSync
 * PUBLIC:     __P((Tcl_Interp *, int, Tcl_Obj * CONST *, DB_ENV *));
 */
int
tcl_RepSync(interp, objc, objv, dbenv)
	Tcl_Interp *interp;
	int objc;
	Tcl_Obj *CONST objv[];
	DB_ENV *dbenv;
{
	int ret;

	if (objc != 2) {
		Tcl_WrongNumArgs(interp, 2, objv, "");
		return TCL_ERROR;
	}

	_debug_check();
	ret = dbenv->rep_sync(dbenv, 0);
	return (_ReturnSetup(interp, ret, DB_RETOK_STD(ret), "env rep_sync"));
}
#endif

#ifdef CONFIG_TEST
/*
 * tcl_RepLease --
 *	Call DB_ENV->rep_set_lease().
 *
 * PUBLIC: int tcl_RepLease  __P((Tcl_Interp *, int, Tcl_Obj * CONST *,
 * PUBLIC:    DB_ENV *));
 */
int
tcl_RepLease(interp, objc, objv, dbenv)
	Tcl_Interp *interp;		/* Interpreter */
	int objc;			/* How many arguments? */
	Tcl_Obj *CONST objv[];		/* The argument objects */
	DB_ENV *dbenv;
{
	u_int32_t clock_fast, clock_slow, nsites, timeout;
	int result, ret;

	COMPQUIET(clock_fast, 0);
	COMPQUIET(clock_slow, 0);

	if (objc != 4 && objc != 2) {
		Tcl_WrongNumArgs(interp, 1, objv, "{nsites timeout fast slow}");
		return (TCL_ERROR);
	}

	if ((result = _GetUInt32(interp, objv[0], &nsites)) != TCL_OK)
		return (result);
	if ((result = _GetUInt32(interp, objv[1], &timeout)) != TCL_OK)
		return (result);
	if (objc == 4) {
		if ((result = _GetUInt32(interp, objv[2], &clock_fast))
		    != TCL_OK)
			return (result);
		if ((result = _GetUInt32(interp, objv[3], &clock_slow))
		    != TCL_OK)
			return (result);
	}
	ret = dbenv->rep_set_nsites(dbenv, nsites);
	result = _ReturnSetup(interp, ret, DB_RETOK_STD(ret),
	    "rep_set_nsites");
	if (result != TCL_OK)
		return (result);
	ret = dbenv->rep_set_timeout(dbenv, DB_REP_LEASE_TIMEOUT,
	    (db_timeout_t)timeout);
	result = _ReturnSetup(interp, ret, DB_RETOK_STD(ret),
	    "rep_set_timeout");
	ret = dbenv->rep_set_config(dbenv, DB_REP_CONF_LEASE, 1);
	result = _ReturnSetup(interp, ret, DB_RETOK_STD(ret),
	    "rep_set_config");
	if (result != TCL_OK)
		return (result);
	if (objc == 4)
		ret = dbenv->rep_set_clockskew(dbenv, clock_fast, clock_slow);
	return (_ReturnSetup(interp, ret, DB_RETOK_STD(ret),
	    "env rep_set_lease"));
}
#endif

#ifdef CONFIG_TEST
/*
 * tcl_RepLimit --
 *	Call DB_ENV->rep_set_limit().
 *
 * PUBLIC: int tcl_RepLimit
 * PUBLIC:     __P((Tcl_Interp *, int, Tcl_Obj * CONST *, DB_ENV *));
 */
int
tcl_RepLimit(interp, objc, objv, dbenv)
	Tcl_Interp *interp;		/* Interpreter */
	int objc;			/* How many arguments? */
	Tcl_Obj *CONST objv[];		/* The argument objects */
	DB_ENV *dbenv;			/* Environment pointer */
{
	int result, ret;
	u_int32_t bytes, gbytes;

	if (objc != 4) {
		Tcl_WrongNumArgs(interp, 4, objv, "gbytes bytes");
		return (TCL_ERROR);
	}

	if ((result = _GetUInt32(interp, objv[2], &gbytes)) != TCL_OK)
		return (result);
	if ((result = _GetUInt32(interp, objv[3], &bytes)) != TCL_OK)
		return (result);

	_debug_check();
	if ((ret = dbenv->rep_set_limit(dbenv, gbytes, bytes)) != 0)
		return (_ReturnSetup(interp, ret, DB_RETOK_STD(ret),
		    "env set_rep_limit"));

	return (_ReturnSetup(interp,
	    ret, DB_RETOK_STD(ret), "env set_rep_limit"));
}
#endif

#ifdef CONFIG_TEST
/*
 * tcl_RepRequest --
 *	Call DB_ENV->rep_set_request().
 *
 * PUBLIC: int tcl_RepRequest
 * PUBLIC:     __P((Tcl_Interp *, int, Tcl_Obj * CONST *, DB_ENV *));
 */
int
tcl_RepRequest(interp, objc, objv, dbenv)
	Tcl_Interp *interp;		/* Interpreter */
	int objc;			/* How many arguments? */
	Tcl_Obj *CONST objv[];		/* The argument objects */
	DB_ENV *dbenv;			/* Environment pointer */
{
	int result, ret;
	long min, max;

	if (objc != 4) {
		Tcl_WrongNumArgs(interp, 4, objv, "min max");
		return (TCL_ERROR);
	}

	if ((result = Tcl_GetLongFromObj(interp, objv[2], &min)) != TCL_OK)
		return (result);
	if ((result = Tcl_GetLongFromObj(interp, objv[3], &max)) != TCL_OK)
		return (result);

	_debug_check();
	if ((ret = dbenv->rep_set_request(dbenv, (db_timeout_t)min,
	    (db_timeout_t)max)) != 0)
		return (_ReturnSetup(interp, ret, DB_RETOK_STD(ret),
		    "env rep_request"));

	return (_ReturnSetup(interp,
	    ret, DB_RETOK_STD(ret), "env rep_request"));
}
#endif

#ifdef CONFIG_TEST
/*
 * tcl_RepNoarchiveTimeout --
 *	Reset the master update timer, to allow immediate log archiving.
 *
 * PUBLIC: int tcl_RepNoarchiveTimeout
 * PUBLIC:     __P((Tcl_Interp *, DB_ENV *));
 */
int
tcl_RepNoarchiveTimeout(interp, dbenv)
	Tcl_Interp *interp;		/* Interpreter */
	DB_ENV *dbenv;			/* Environment pointer */
{
	ENV *env;
	REGENV *renv;
	REGINFO *infop;

	env = dbenv->env;

	_debug_check();
	infop = env->reginfo;
	renv = infop->primary;
	REP_SYSTEM_LOCK(env);
	F_CLR(renv, DB_REGENV_REPLOCKED);
	renv->op_timestamp = 0;
	REP_SYSTEM_UNLOCK(env);

	return (_ReturnSetup(interp,
	    0, DB_RETOK_STD(0), "env test force noarchive_timeout"));
}
#endif

#ifdef CONFIG_TEST
/*
 * tcl_RepTransport --
 *	Call DB_ENV->rep_set_transport().
 *
 * PUBLIC: int tcl_RepTransport  __P((Tcl_Interp *, int, Tcl_Obj * CONST *,
 * PUBLIC:    DB_ENV *, DBTCL_INFO *));
 *
 *	Note that this normally can/should be achieved as an argument to
 * berkdb env, but we need to test changing the transport function on
 * the fly.
 */
int
tcl_RepTransport(interp, objc, objv, dbenv, ip)
	Tcl_Interp *interp;		/* Interpreter */
	int objc;			/* How many arguments? */
	Tcl_Obj *CONST objv[];		/* The argument objects */
	DB_ENV *dbenv;
	DBTCL_INFO *ip;
{
	int intarg, result, ret;

	if (objc != 2) {
		Tcl_WrongNumArgs(interp, 2, objv, "{id transport_func");
		return (TCL_ERROR);
	}

	/*
	 * Store the objects containing the machine ID
	 * and the procedure name.  We don't need to crack
	 * the send procedure out now, but we do convert the
	 * machine ID to an int, since rep_set_transport needs
	 * it.  Even so, it'll be easier later to deal with
	 * the Tcl_Obj *, so we save that, not the int.
	 *
	 * Note that we Tcl_IncrRefCount both objects
	 * independently;  Tcl is free to discard the list
	 * that they're bundled into.
	 */

	/*
	 * Check that the machine ID is an int.  Note that
	 * we do want to use GetIntFromObj;  the machine
	 * ID is explicitly an int, not a u_int32_t.
	 */
	if (ip->i_rep_eid != NULL) {
		Tcl_DecrRefCount(ip->i_rep_eid);
	}
	ip->i_rep_eid = objv[0];
	Tcl_IncrRefCount(ip->i_rep_eid);
	result = Tcl_GetIntFromObj(interp,
	    ip->i_rep_eid, &intarg);
	if (result != TCL_OK)
		return (result);

	if (ip->i_rep_send != NULL) {
		Tcl_DecrRefCount(ip->i_rep_send);
	}
	ip->i_rep_send = objv[1];
	Tcl_IncrRefCount(ip->i_rep_send);
	_debug_check();
	ret = dbenv->rep_set_transport(dbenv, intarg, tcl_rep_send);
	return (_ReturnSetup(interp, ret, DB_RETOK_STD(ret),
	    "env rep_transport"));
}
#endif

#ifdef CONFIG_TEST
/*
 * tcl_RepStart --
 *	Call DB_ENV->rep_start().
 *
 * PUBLIC: int tcl_RepStart
 * PUBLIC:     __P((Tcl_Interp *, int, Tcl_Obj * CONST *, DB_ENV *));
 *
 *	Note that this normally can/should be achieved as an argument to
 * berkdb env, but we need to test forcible upgrading of clients, which
 * involves calling this on an open environment handle.
 */
int
tcl_RepStart(interp, objc, objv, dbenv)
	Tcl_Interp *interp;		/* Interpreter */
	int objc;			/* How many arguments? */
	Tcl_Obj *CONST objv[];		/* The argument objects */
	DB_ENV *dbenv;
{
	static const char *tclrpstrt[] = {
		"-client",
		"-master",
		NULL
	};
	enum tclrpstrt {
		TCL_RPSTRT_CLIENT,
		TCL_RPSTRT_MASTER
	};
	char *arg;
	int i, optindex, ret;
	u_int32_t flag;

	flag = 0;

	if (objc != 3) {
		Tcl_WrongNumArgs(interp, 3, objv, "[-master/-client]");
		return (TCL_ERROR);
	}

	i = 2;
	while (i < objc) {
		if (Tcl_GetIndexFromObj(interp, objv[i], tclrpstrt,
		    "option", TCL_EXACT, &optindex) != TCL_OK) {
			arg = Tcl_GetStringFromObj(objv[i], NULL);
			if (arg[0] == '-')
				return (IS_HELP(objv[i]));
			else
				Tcl_ResetResult(interp);
			break;
		}
		i++;
		switch ((enum tclrpstrt)optindex) {
		case TCL_RPSTRT_CLIENT:
			flag = DB_REP_CLIENT;
			break;
		case TCL_RPSTRT_MASTER:
			flag = DB_REP_MASTER;
			break;
		}
	}

	_debug_check();
	ret = dbenv->rep_start(dbenv, NULL, flag);
	return (_ReturnSetup(interp, ret, DB_RETOK_STD(ret), "env rep_start"));
}
#endif

#ifdef CONFIG_TEST
/*
 * tcl_RepProcessMessage --
 *	Call DB_ENV->rep_process_message().
 *
 * PUBLIC: int tcl_RepProcessMessage
 * PUBLIC:     __P((Tcl_Interp *, int, Tcl_Obj * CONST *, DB_ENV *));
 */
int
tcl_RepProcessMessage(interp, objc, objv, dbenv)
	Tcl_Interp *interp;		/* Interpreter */
	int objc;			/* How many arguments? */
	Tcl_Obj *CONST objv[];		/* The argument objects */
	DB_ENV *dbenv;			/* Environment pointer */
{
	DBT control, rec;
	DB_LSN permlsn;
	Tcl_Obj *lsnlist, *myobjv[2], *res;
	void *ctmp, *rtmp;
	char *msg;
	int eid;
	int freectl, freerec, myobjc, result, ret;

	if (objc != 5) {
		Tcl_WrongNumArgs(interp, 5, objv, "id control rec");
		return (TCL_ERROR);
	}
	freectl = freerec = 0;

	memset(&control, 0, sizeof(control));
	memset(&rec, 0, sizeof(rec));

	if ((result = Tcl_GetIntFromObj(interp, objv[2], &eid)) != TCL_OK)
		return (result);

	ret = _CopyObjBytes(interp, objv[3], &ctmp,
	    &control.size, &freectl);
	if (ret != 0) {
		result = _ReturnSetup(interp, ret,
		    DB_RETOK_REPPMSG(ret), "rep_proc_msg");
		return (result);
	}
	control.data = ctmp;
	ret = _CopyObjBytes(interp, objv[4], &rtmp,
	    &rec.size, &freerec);
	if (ret != 0) {
		result = _ReturnSetup(interp, ret,
		    DB_RETOK_REPPMSG(ret), "rep_proc_msg");
		goto out;
	}
	rec.data = rtmp;
	_debug_check();
	ret = dbenv->rep_process_message(dbenv, &control, &rec, eid, &permlsn);
	/*
	 * !!!
	 * The TCL API diverges from the C++/Java APIs here.  For us, it
	 * is OK to get DUPMASTER and HOLDELECTION for testing purposes.
	 */
	result = _ReturnSetup(interp, ret,
	    DB_RETOK_REPPMSG(ret) || ret == DB_REP_DUPMASTER ||
	    ret == DB_REP_HOLDELECTION,
	    "env rep_process_message");

	if (result != TCL_OK)
		goto out;

	/*
	 * We have a valid return.  We need to return a variety of information.
	 * It will be one of the following:
	 * {0 0} -  Make a 0 return a list for consistent return structure.
	 * {DUPMASTER 0} -  DUPMASTER, no other info needed.
	 * {HOLDELECTION 0} -  HOLDELECTION, no other info needed.
	 * {NEWMASTER #} - NEWMASTER and its ID.
	 * {NEWSITE 0} - NEWSITE, no other info needed.
	 * {IGNORE {LSN list}} - IGNORE and this msg's LSN.
	 * {ISPERM {LSN list}} - ISPERM and the perm LSN.
	 * {NOTPERM {LSN list}} - NOTPERM and this msg's LSN.
	 */
	myobjc = 2;
	switch (ret) {
	case 0:
		myobjv[0] = Tcl_NewIntObj(0);
		myobjv[1] = Tcl_NewIntObj(0);
		break;
	case DB_REP_DUPMASTER:
		myobjv[0] = Tcl_NewByteArrayObj(
		    (u_char *)"DUPMASTER", (int)strlen("DUPMASTER"));
		myobjv[1] = Tcl_NewIntObj(0);
		break;
	case DB_REP_HOLDELECTION:
		myobjv[0] = Tcl_NewByteArrayObj(
		    (u_char *)"HOLDELECTION", (int)strlen("HOLDELECTION"));
		myobjv[1] = Tcl_NewIntObj(0);
		break;
	case DB_REP_IGNORE:
		myobjv[0] = Tcl_NewLongObj((long)permlsn.file);
		myobjv[1] = Tcl_NewLongObj((long)permlsn.offset);
		lsnlist = Tcl_NewListObj(myobjc, myobjv);
		myobjv[0] = Tcl_NewByteArrayObj(
		    (u_char *)"IGNORE", (int)strlen("IGNORE"));
		myobjv[1] = lsnlist;
		break;
	case DB_REP_ISPERM:
		myobjv[0] = Tcl_NewLongObj((long)permlsn.file);
		myobjv[1] = Tcl_NewLongObj((long)permlsn.offset);
		lsnlist = Tcl_NewListObj(myobjc, myobjv);
		myobjv[0] = Tcl_NewByteArrayObj(
		    (u_char *)"ISPERM", (int)strlen("ISPERM"));
		myobjv[1] = lsnlist;
		break;
	case DB_REP_NEWSITE:
		myobjv[0] = Tcl_NewByteArrayObj(
		    (u_char *)"NEWSITE", (int)strlen("NEWSITE"));
		myobjv[1] = Tcl_NewIntObj(0);
		break;
	case DB_REP_NOTPERM:
		myobjv[0] = Tcl_NewLongObj((long)permlsn.file);
		myobjv[1] = Tcl_NewLongObj((long)permlsn.offset);
		lsnlist = Tcl_NewListObj(myobjc, myobjv);
		myobjv[0] = Tcl_NewByteArrayObj(
		    (u_char *)"NOTPERM", (int)strlen("NOTPERM"));
		myobjv[1] = lsnlist;
		break;
	default:
		msg = db_strerror(ret);
		Tcl_AppendResult(interp, msg, NULL);
		Tcl_SetErrorCode(interp, "BerkeleyDB", msg, NULL);
		result = TCL_ERROR;
		goto out;
	}
	res = Tcl_NewListObj(myobjc, myobjv);
	if (res != NULL)
		Tcl_SetObjResult(interp, res);
out:
	if (freectl)
		__os_free(NULL, ctmp);
	if (freerec)
		__os_free(NULL, rtmp);

	return (result);
}
#endif

#ifdef CONFIG_TEST
/*
 * tcl_RepStat --
 *	Call DB_ENV->rep_stat().
 *
 * PUBLIC: int tcl_RepStat
 * PUBLIC:     __P((Tcl_Interp *, int, Tcl_Obj * CONST *, DB_ENV *));
 */
int
tcl_RepStat(interp, objc, objv, dbenv)
	Tcl_Interp *interp;		/* Interpreter */
	int objc;			/* How many arguments? */
	Tcl_Obj *CONST objv[];		/* The argument objects */
	DB_ENV *dbenv;
{
	DB_REP_STAT *sp;
	Tcl_Obj *myobjv[2], *res, *thislist, *lsnlist;
	u_int32_t flag;
	int myobjc, result, ret;
	char *arg;

	flag = 0;
	result = TCL_OK;

	if (objc > 3) {
		Tcl_WrongNumArgs(interp, 2, objv, NULL);
		return (TCL_ERROR);
	}
	if (objc == 3) {
		arg = Tcl_GetStringFromObj(objv[2], NULL);
		if (strcmp(arg, "-clear") == 0)
			flag = DB_STAT_CLEAR;
		else {
			Tcl_SetResult(interp,
			    "db stat: unknown arg", TCL_STATIC);
			return (TCL_ERROR);
		}
	}

	_debug_check();
	ret = dbenv->rep_stat(dbenv, &sp, flag);
	result = _ReturnSetup(interp, ret, DB_RETOK_STD(ret),
	    "rep stat");
	if (result == TCL_ERROR)
		return (result);

	/*
	 * Have our stats, now construct the name value
	 * list pairs and free up the memory.
	 */
	res = Tcl_NewObj();
#ifdef HAVE_STATISTICS
	/*
	 * MAKE_STAT_* assumes 'res' and 'error' label.
	 */
	if (sp->st_status == DB_REP_MASTER)
		MAKE_STAT_LIST("Master", 1);
	else
		MAKE_STAT_LIST("Client", 1);
	MAKE_STAT_LSN("Next LSN expected", &sp->st_next_lsn);
	MAKE_STAT_LSN("First missed LSN", &sp->st_waiting_lsn);
	MAKE_STAT_LSN("Maximum permanent LSN", &sp->st_max_perm_lsn);
	MAKE_STAT_LIST("Bulk buffer fills", sp->st_bulk_fills);
	MAKE_STAT_LIST("Bulk buffer overflows", sp->st_bulk_overflows);
	MAKE_STAT_LIST("Bulk records stored", sp->st_bulk_records);
	MAKE_STAT_LIST("Bulk buffer transfers", sp->st_bulk_transfers);
	MAKE_STAT_LIST("Client service requests", sp->st_client_svc_req);
	MAKE_STAT_LIST("Client service req misses", sp->st_client_svc_miss);
	MAKE_STAT_LIST("Client rerequests", sp->st_client_rerequests);
	MAKE_STAT_LIST("Duplicate master conditions", sp->st_dupmasters);
	MAKE_STAT_LIST("Environment ID", sp->st_env_id);
	MAKE_STAT_LIST("Environment priority", sp->st_env_priority);
	MAKE_STAT_LIST("Generation number", sp->st_gen);
	MAKE_STAT_LIST("Election generation number", sp->st_egen);
	MAKE_STAT_LIST("Startup complete", sp->st_startup_complete);
	MAKE_STAT_LIST("Duplicate log records received", sp->st_log_duplicated);
	MAKE_STAT_LIST("Current log records queued", sp->st_log_queued);
	MAKE_STAT_LIST("Maximum log records queued", sp->st_log_queued_max);
	MAKE_STAT_LIST("Total log records queued", sp->st_log_queued_total);
	MAKE_STAT_LIST("Log records received", sp->st_log_records);
	MAKE_STAT_LIST("Log records requested", sp->st_log_requested);
	MAKE_STAT_LIST("Master environment ID", sp->st_master);
	MAKE_STAT_LIST("Master changes", sp->st_master_changes);
	MAKE_STAT_LIST("Messages with bad generation number",
	    sp->st_msgs_badgen);
	MAKE_STAT_LIST("Messages processed", sp->st_msgs_processed);
	MAKE_STAT_LIST("Messages ignored for recovery", sp->st_msgs_recover);
	MAKE_STAT_LIST("Message send failures", sp->st_msgs_send_failures);
	MAKE_STAT_LIST("Messages sent", sp->st_msgs_sent);
	MAKE_STAT_LIST("New site messages", sp->st_newsites);
	MAKE_STAT_LIST("Number of sites in replication group", sp->st_nsites);
	MAKE_STAT_LIST("Transmission limited", sp->st_nthrottles);
	MAKE_STAT_LIST("Outdated conditions", sp->st_outdated);
	MAKE_STAT_LIST("Transactions applied", sp->st_txns_applied);
	MAKE_STAT_LIST("Next page expected", sp->st_next_pg);
	MAKE_STAT_LIST("First missed page", sp->st_waiting_pg);
	MAKE_STAT_LIST("Duplicate pages received", sp->st_pg_duplicated);
	MAKE_STAT_LIST("Pages received", sp->st_pg_records);
	MAKE_STAT_LIST("Pages requested", sp->st_pg_requested);
	MAKE_STAT_LIST("Elections held", sp->st_elections);
	MAKE_STAT_LIST("Elections won", sp->st_elections_won);
	MAKE_STAT_LIST("Election phase", sp->st_election_status);
	MAKE_STAT_LIST("Election winner", sp->st_election_cur_winner);
	MAKE_STAT_LIST("Election generation number", sp->st_election_gen);
	MAKE_STAT_LSN("Election max LSN", &sp->st_election_lsn);
	MAKE_STAT_LIST("Election sites", sp->st_election_nsites);
	MAKE_STAT_LIST("Election nvotes", sp->st_election_nvotes);
	MAKE_STAT_LIST("Election priority", sp->st_election_priority);
	MAKE_STAT_LIST("Election tiebreaker", sp->st_election_tiebreaker);
	MAKE_STAT_LIST("Election votes", sp->st_election_votes);
	MAKE_STAT_LIST("Election seconds", sp->st_election_sec);
	MAKE_STAT_LIST("Election usecs", sp->st_election_usec);
	MAKE_STAT_LIST("Start-sync operations delayed",
	    sp->st_startsync_delayed);
	MAKE_STAT_LIST("Maximum lease seconds", sp->st_max_lease_sec);
	MAKE_STAT_LIST("Maximum lease usecs", sp->st_max_lease_usec);
#endif

	Tcl_SetObjResult(interp, res);
error:
	__os_ufree(dbenv->env, sp);
	return (result);
}

/*
 * tcl_RepMgr --
 *	Configure and start the Replication Manager.
 *
 * PUBLIC: int tcl_RepMgr
 * PUBLIC:     __P((Tcl_Interp *, int, Tcl_Obj * CONST *, DB_ENV *));
 */
int
tcl_RepMgr(interp, objc, objv, dbenv)
	Tcl_Interp *interp;		/* Interpreter */
	int objc;			/* How many arguments? */
	Tcl_Obj *CONST objv[];		/* The argument objects */
	DB_ENV *dbenv;			/* Environment pointer */
{
	static const char *rmgr[] = {
		"-ack",
		"-local",
		"-msgth",
		"-nsites",
		"-pri",
		"-remote",
		"-start",
		"-timeout",
		NULL
	};
	enum rmgr {
		RMGR_ACK,
		RMGR_LOCAL,
		RMGR_MSGTH,
		RMGR_NSITES,
		RMGR_PRI,
		RMGR_REMOTE,
		RMGR_START,
		RMGR_TIMEOUT
	};
	Tcl_Obj **myobjv;
	long to;
	int ack, i, myobjc, optindex, result, ret, totype;
	u_int32_t msgth, remote_flag, start_flag, uintarg;
	char *arg;

	result = TCL_OK;
	ack = ret = totype = 0;
	msgth = 1;
	remote_flag = start_flag = 0;

	if (objc <= 2) {
		Tcl_WrongNumArgs(interp, 2, objv, "?args?");
		return (TCL_ERROR);
	}
	/*
	 * Get the command name index from the object based on the bdbcmds
	 * defined above.
	 */
	i = 2;
	while (i < objc) {
		Tcl_ResetResult(interp);
		if (Tcl_GetIndexFromObj(interp, objv[i], rmgr, "option",
		    TCL_EXACT, &optindex) != TCL_OK) {
			result = IS_HELP(objv[i]);
			goto error;
		}
		i++;
		switch ((enum rmgr)optindex) {
		case RMGR_ACK:
			if (i >= objc) {
				Tcl_WrongNumArgs(interp, 2, objv,
				    "?-ack policy?");
				result = TCL_ERROR;
				break;
			}
			arg = Tcl_GetStringFromObj(objv[i++], NULL);
			if (strcmp(arg, "all") == 0)
				ack = DB_REPMGR_ACKS_ALL;
			else if (strcmp(arg, "allpeers") == 0)
				ack = DB_REPMGR_ACKS_ALL_PEERS;
			else if (strcmp(arg, "none") == 0)
				ack = DB_REPMGR_ACKS_NONE;
			else if (strcmp(arg, "one") == 0)
				ack = DB_REPMGR_ACKS_ONE;
			else if (strcmp(arg, "onepeer") == 0)
				ack = DB_REPMGR_ACKS_ONE_PEER;
			else if (strcmp(arg, "quorum") == 0)
				ack = DB_REPMGR_ACKS_QUORUM;
			else {
				Tcl_AddErrorInfo(interp,
				    "ack: illegal policy");
				result = TCL_ERROR;
				break;
			}
			_debug_check();
			ret = dbenv->repmgr_set_ack_policy(dbenv, ack);
			result = _ReturnSetup(interp, ret, DB_RETOK_STD(ret),
			    "ack");
			break;
		case RMGR_LOCAL:
			result = Tcl_ListObjGetElements(interp, objv[i],
			    &myobjc, &myobjv);
			if (result == TCL_OK)
				i++;
			else
				break;
			if (myobjc != 2) {
				Tcl_WrongNumArgs(interp, 2, objv,
				    "?-local {host port}?");
				result = TCL_ERROR;
				break;
			}
			arg = Tcl_GetStringFromObj(myobjv[0], NULL);
			if ((result = _GetUInt32(interp, myobjv[1], &uintarg))
			    != TCL_OK)
				break;
			_debug_check();
			/*
			 * No flags for now.
			 */
			ret = dbenv->repmgr_set_local_site(dbenv,
			    arg, uintarg, 0);
			result = _ReturnSetup(interp, ret, DB_RETOK_STD(ret),
			    "repmgr_set_local_site");
			break;
		case RMGR_MSGTH:
			if (i >= objc) {
				Tcl_WrongNumArgs(
				    interp, 2, objv, "?-msgth nth?");
				result = TCL_ERROR;
				break;
			}
			result = _GetUInt32(interp, objv[i++], &msgth);
			break;
		case RMGR_NSITES:
			if (i >= objc) {
				Tcl_WrongNumArgs(interp, 2, objv,
				    "?-nsites num_sites?");
				result = TCL_ERROR;
				break;
			}
			result = _GetUInt32(interp, objv[i++], &uintarg);
			if (result == TCL_OK) {
				_debug_check();
				ret = dbenv->
				    rep_set_nsites(dbenv, uintarg);
			}
			break;
		case RMGR_PRI:
			if (i >= objc) {
				Tcl_WrongNumArgs(interp, 2, objv,
				    "?-pri priority?");
				result = TCL_ERROR;
				break;
			}
			result = _GetUInt32(interp, objv[i++], &uintarg);
			if (result == TCL_OK) {
				_debug_check();
				ret = dbenv->
				    rep_set_priority(dbenv, uintarg);
			}
			break;
		case RMGR_REMOTE:
			result = Tcl_ListObjGetElements(interp, objv[i],
			    &myobjc, &myobjv);
			if (result == TCL_OK)
				i++;
			else
				break;
			if (myobjc != 2 && myobjc != 3) {
				Tcl_WrongNumArgs(interp, 2, objv,
				    "?-remote {host port [peer]}?");
				result = TCL_ERROR;
				break;
			}
			/*
			 * Get the flag first so we can reuse 'arg'.
			 */
			if (myobjc == 3) {
				arg = Tcl_GetStringFromObj(myobjv[2], NULL);
				if (strcmp(arg, "peer") == 0)
					remote_flag = DB_REPMGR_PEER;
				else {
					Tcl_AddErrorInfo(interp,
					    "remote: illegal flag");
					result = TCL_ERROR;
					break;
				}
			}
			arg = Tcl_GetStringFromObj(myobjv[0], NULL);
			if ((result = _GetUInt32(interp, myobjv[1], &uintarg))
			    != TCL_OK)
				break;
			_debug_check();
			ret = dbenv->repmgr_add_remote_site(dbenv,
			    arg, uintarg, NULL, remote_flag);
			result = _ReturnSetup(interp, ret, DB_RETOK_STD(ret),
			    "repmgr_add_remote_site");
			break;
		case RMGR_START:
			if (i >= objc) {
				Tcl_WrongNumArgs(
				    interp, 2, objv, "?-start state?");
				result = TCL_ERROR;
				break;
			}
			arg = Tcl_GetStringFromObj(objv[i++], NULL);
			if (strcmp(arg, "master") == 0)
				start_flag = DB_REP_MASTER;
			else if (strcmp(arg, "client") == 0)
				start_flag = DB_REP_CLIENT;
			else if (strcmp(arg, "elect") == 0)
				start_flag = DB_REP_ELECTION;
			else {
				Tcl_AddErrorInfo(
				    interp, "start: illegal state");
				result = TCL_ERROR;
				break;
			}
			/*
			 * Some config functions need to be called
			 * before repmgr_start.  So finish parsing all
			 * the args and call repmgr_start at the end.
			 */
			break;
		case RMGR_TIMEOUT:
			result = Tcl_ListObjGetElements(interp, objv[i],
			    &myobjc, &myobjv);
			if (result == TCL_OK)
				i++;
			else
				break;
			if (myobjc != 2) {
				Tcl_WrongNumArgs(interp, 2, objv,
				    "?-timeout {type to}?");
				result = TCL_ERROR;
				break;
			}
			arg = Tcl_GetStringFromObj(myobjv[0], NULL);
			if (strcmp(arg, "ack") == 0)
				totype = DB_REP_ACK_TIMEOUT;
			else if (strcmp(arg, "elect") == 0)
				totype = DB_REP_ELECTION_TIMEOUT;
			else if (strcmp(arg, "elect_retry") == 0)
				totype = DB_REP_ELECTION_RETRY;
			else if (strcmp(arg, "conn_retry") == 0)
				totype = DB_REP_CONNECTION_RETRY;
			else {
				Tcl_AddErrorInfo(interp,
				    "timeout: illegal type");
				result = TCL_ERROR;
				break;
			}
			if ((result = Tcl_GetLongFromObj(
			   interp, myobjv[1], &to)) != TCL_OK)
				break;
			_debug_check();
			ret = dbenv->rep_set_timeout(dbenv, totype,
			    (db_timeout_t)to);
			result = _ReturnSetup(interp, ret, DB_RETOK_STD(ret),
			    "rep_set_timeout");
			break;
		}
		/*
		 * If, at any time, parsing the args we get an error,
		 * bail out and return.
		 */
		if (result != TCL_OK)
			goto error;
	}
	/*
	 * Only call repmgr_start if needed.  The user may use this
	 * call just to reconfigure, change policy, etc.
	 */
	if (start_flag != 0 && result == TCL_OK) {
		_debug_check();
		ret = dbenv->repmgr_start(dbenv, (int)msgth, start_flag);
		result = _ReturnSetup(
		    interp, ret, DB_RETOK_STD(ret), "repmgr_start");
	}
error:
	return (result);
}

/*
 * tcl_RepMgrStat --
 *	Call DB_ENV->repmgr_stat().
 *
 * PUBLIC: int tcl_RepMgrStat
 * PUBLIC:     __P((Tcl_Interp *, int, Tcl_Obj * CONST *, DB_ENV *));
 */
int
tcl_RepMgrStat(interp, objc, objv, dbenv)
	Tcl_Interp *interp;		/* Interpreter */
	int objc;			/* How many arguments? */
	Tcl_Obj *CONST objv[];		/* The argument objects */
	DB_ENV *dbenv;
{
	DB_REPMGR_STAT *sp;
	Tcl_Obj *res;
	u_int32_t flag;
	int result, ret;
	char *arg;

	flag = 0;
	result = TCL_OK;

	if (objc > 3) {
		Tcl_WrongNumArgs(interp, 2, objv, NULL);
		return (TCL_ERROR);
	}
	if (objc == 3) {
		arg = Tcl_GetStringFromObj(objv[2], NULL);
		if (strcmp(arg, "-clear") == 0)
			flag = DB_STAT_CLEAR;
		else {
			Tcl_SetResult(interp,
			    "db stat: unknown arg", TCL_STATIC);
			return (TCL_ERROR);
		}
	}

	_debug_check();
	ret = dbenv->repmgr_stat(dbenv, &sp, flag);
	result = _ReturnSetup(interp, ret, DB_RETOK_STD(ret),
	    "repmgr stat");
	if (result == TCL_ERROR)
		return (result);

	/*
	 * Have our stats, now construct the name value
	 * list pairs and free up the memory.
	 */
	res = Tcl_NewObj();
#ifdef HAVE_STATISTICS
	/*
	 * MAKE_STAT_* assumes 'res' and 'error' label.
	 */
	MAKE_STAT_LIST("Acknowledgement failures", sp->st_perm_failed);
	MAKE_STAT_LIST("Messages delayed", sp->st_msgs_queued);
	MAKE_STAT_LIST("Messages discarded", sp->st_msgs_dropped);
	MAKE_STAT_LIST("Connections dropped", sp->st_connection_drop);
	MAKE_STAT_LIST("Failed re-connects", sp->st_connect_fail);
#endif

	Tcl_SetObjResult(interp, res);
error:
	__os_ufree(dbenv->env, sp);
	return (result);
}
#endif
