/*-
 * See the file LICENSE for redistribution information.
 *
 * Copyright (c) 2001, 2011 Oracle and/or its affiliates.  All rights reserved.
 *
 * $Id$
 */

#include "db_config.h"

#include "db_int.h"
#include "dbinc/db_page.h"
#include "dbinc/lock.h"
#include "dbinc/mp.h"
#include "dbinc/txn.h"
#include "dbinc/db_am.h"

typedef struct __txn_event TXN_EVENT;
struct __txn_event {
	TXN_EVENT_T op;
	TAILQ_ENTRY(__txn_event) links;
	union {
		struct {
			/* Delayed close. */
			DB *dbp;
		} c;
		struct {
			/* Delayed remove. */
			char *name;
			u_int8_t *fileid;
			int inmem;
		} r;
		struct {
			/* Lock event. */
			DB_LOCK lock;
			DB_LOCKER *locker;
			DB *dbp;
		} t;
	} u;
};

#define	TXN_TOP_PARENT(txn) do {					\
	while (txn/**/->parent/**/ != NULL/*void**//*int*/)					\
		txn/**/ = txn/**/->parent/**//**/;					\
} while (0/*int*/)

static void __clear_fe_watermark __P((DB_TXN *, DB *));

/*
 * __txn_closeevent --
 *
 * Creates a close event that can be added to the [so-called] commit list, so
 * that we can redo a failed DB handle close once we've aborted the transaction.
 *
 * PUBLIC: int __txn_closeevent __P((ENV *, DB_TXN *, DB *));
 */
int
__txn_closeevent(env, txn, dbp)
	ENV *env;
	DB_TXN *txn;
	DB *dbp;
{
	int ret;
	TXN_EVENT *e;

	e/*TXN_EVENT*, local*/ = NULL/*void**//*TXN_EVENT*, local*/;
	if ((ret/*int, local*/ = __os_calloc/**/(env/*ENV*, local*/, 1/*int*/, sizeof(TXN_EVENT)/*size_t*/, &e/*TXN_EVENT*, local*//*TXN_EVENT***/)/**//*int, local*/)/*int, local*/ != 0/*int*//*int*/)
		return (ret/*int, local*/)/*int, local*/;

	e/*TXN_EVENT*, local*/->u/**/.c/**/.dbp/**/ = dbp/*DB*, local*//*DB*, local*/;
	e/*TXN_EVENT*, local*/->op/**/ = TXN_CLOSE/**//**/;
	TXN_TOP_PARENT/**/(txn/*DB_TXN*, local*/)/**/;
	TAILQ_INSERT_TAIL/**/(&txn/*DB_TXN*, local*/->events/**//**/, e/*TXN_EVENT*, local*/, links/**/)/**/;

	return (0/*int*/)/*int*/;
}

/*
 * __txn_remevent --
 *
 * Creates a remove event that can be added to the commit list.
 *
 * PUBLIC: int __txn_remevent __P((ENV *,
 * PUBLIC:       DB_TXN *, const char *, u_int8_t *, int));
 */
int
__txn_remevent(env, txn, name, fileid, inmem)
	ENV *env;
	DB_TXN *txn;
	const char *name;
	u_int8_t *fileid;
	int inmem;
{
	int ret;
	TXN_EVENT *e;

	e/*TXN_EVENT*, local*/ = NULL/*void**//*TXN_EVENT*, local*/;
	if ((ret/*int, local*/ = __os_calloc/**/(env/*ENV*, local*/, 1/*int*/, sizeof(TXN_EVENT)/*size_t*/, &e/*TXN_EVENT*, local*//*TXN_EVENT***/)/**//*int, local*/)/*int, local*/ != 0/*int*//*int*/)
		return (ret/*int, local*/)/*int, local*/;

	if ((ret/*int, local*/ = __os_strdup/**/(env/*ENV*, local*/, name/*const char*, local*/, &e/*TXN_EVENT*, local*/->u/**/.r/**/.name/**//**/)/**//*int, local*/)/*int, local*/ != 0/*int*//*int*/)
		goto err;

	if (fileid/*u_int8_t*, local*/ != NULL/*void**//*int*/) {
		if ((ret/*int, local*/ = __os_calloc/**/(env/*ENV*, local*/,
		    1/*int*/, DB_FILE_ID_LEN/**/, &e/*TXN_EVENT*, local*/->u/**/.r/**/.fileid/**//**/)/**//*int, local*/)/*int, local*/ != 0/*int*//*int*/) {
			__os_free/**/(env/*ENV*, local*/, e/*TXN_EVENT*, local*/->u/**/.r/**/.name/**/)/**/;
			goto err;
		}
		memcpy/**/(e/*TXN_EVENT*, local*/->u/**/.r/**/.fileid/**/, fileid/*u_int8_t*, local*/, DB_FILE_ID_LEN/**/)/**/;
	}

	e/*TXN_EVENT*, local*/->u/**/.r/**/.inmem/**/ = inmem/*int, local*//*int, local*/;
	e/*TXN_EVENT*, local*/->op/**/ = TXN_REMOVE/**//**/;
	TAILQ_INSERT_TAIL/**/(&txn/*DB_TXN*, local*/->events/**//**/, e/*TXN_EVENT*, local*/, links/**/)/**/;

	return (0/*int*/)/*int*/;

err:	__os_free/**/(env/*ENV*, local*/, e/*TXN_EVENT*, local*/)/**/;

	return (ret/*int, local*/)/*int, local*/;
}

/*
 * __txn_remrem --
 *	Remove a remove event because the remove has been superceeded,
 * by a create of the same name, for example.
 *
 * PUBLIC: void __txn_remrem __P((ENV *, DB_TXN *, const char *));
 */
void
__txn_remrem(env, txn, name)
	ENV *env;
	DB_TXN *txn;
	const char *name;
{
	TXN_EVENT *e, *next_e;

	for (e/*TXN_EVENT*, local*/ = TAILQ_FIRST/**/(&txn/*DB_TXN*, local*/->events/**//**/)/**//*TXN_EVENT*, local*/; e/*TXN_EVENT*, local*/ != NULL/*void**//*int*/; e/*TXN_EVENT*, local*/ = next_e/*TXN_EVENT*, local*//*TXN_EVENT*, local*/) {
		next_e/*TXN_EVENT*, local*/ = TAILQ_NEXT/**/(e/*TXN_EVENT*, local*/, links/**/)/**//*TXN_EVENT*, local*/;
		if (e/*TXN_EVENT*, local*/->op/**/ != TXN_REMOVE/**//*int*/ || strcmp/**/(name/*const char*, local*/, e/*TXN_EVENT*, local*/->u/**/.r/**/.name/**/)/**/ != 0/*int*//*int*//*int*/)
			continue;
		TAILQ_REMOVE/**/(&txn/*DB_TXN*, local*/->events/**//**/, e/*TXN_EVENT*, local*/, links/**/)/**/;
		__os_free/**/(env/*ENV*, local*/, e/*TXN_EVENT*, local*/->u/**/.r/**/.name/**/)/**/;
		if (e/*TXN_EVENT*, local*/->u/**/.r/**/.fileid/**/ != NULL/*void**//*int*/)
			__os_free/**/(env/*ENV*, local*/, e/*TXN_EVENT*, local*/->u/**/.r/**/.fileid/**/)/**/;
		__os_free/**/(env/*ENV*, local*/, e/*TXN_EVENT*, local*/)/**/;
	}

	return;
}

/*
 * __txn_lockevent --
 *
 * Add a lockevent to the commit-queue.  The lock event indicates a locker
 * trade.
 *
 * PUBLIC: int __txn_lockevent __P((ENV *,
 * PUBLIC:     DB_TXN *, DB *, DB_LOCK *, DB_LOCKER *));
 */
int
__txn_lockevent(env, txn, dbp, lock, locker)
	ENV *env;
	DB_TXN *txn;
	DB *dbp;
	DB_LOCK *lock;
	DB_LOCKER *locker;
{
	int ret;
	TXN_EVENT *e;

	if (!LOCKING_ON/**/(env/*ENV*, local*/)/**//*int*/)
		return (0/*int*/)/*int*/;

	e/*TXN_EVENT*, local*/ = NULL/*void**//*TXN_EVENT*, local*/;
	if ((ret/*int, local*/ = __os_calloc/**/(env/*ENV*, local*/, 1/*int*/, sizeof(TXN_EVENT)/*size_t*/, &e/*TXN_EVENT*, local*//*TXN_EVENT***/)/**//*int, local*/)/*int, local*/ != 0/*int*//*int*/)
		return (ret/*int, local*/)/*int, local*/;

	e/*TXN_EVENT*, local*/->u/**/.t/**/.locker/**/ = locker/*DB_LOCKER*, local*//*DB_LOCKER*, local*/;
	e/*TXN_EVENT*, local*/->u/**/.t/**/.lock/**/ = *lock/*DB_LOCK*, local*//*DB_LOCK*//*DB_LOCK*/;
	e/*TXN_EVENT*, local*/->u/**/.t/**/.dbp/**/ = dbp/*DB*, local*//*DB*, local*/;
	e/*TXN_EVENT*, local*/->op/**/ = TXN_TRADE/**//**/;
	/* This event goes on the current transaction, not its parent. */
	TAILQ_INSERT_TAIL/**/(&txn/*DB_TXN*, local*/->events/**//**/, e/*TXN_EVENT*, local*/, links/**/)/**/;
	dbp/*DB*, local*/->cur_txn/**/ = txn/*DB_TXN*, local*//*DB_TXN*, local*/;

	return (0/*int*/)/*int*/;
}

/*
 * __txn_remlock --
 *	Remove a lock event because the locker is going away.  We can remove
 * by lock (using offset) or by locker_id (or by both).
 *
 * PUBLIC: void __txn_remlock __P((ENV *, DB_TXN *, DB_LOCK *, DB_LOCKER *));
 */
void
__txn_remlock(env, txn, lock, locker)
	ENV *env;
	DB_TXN *txn;
	DB_LOCK *lock;
	DB_LOCKER *locker;
{
	TXN_EVENT *e, *next_e;

	for (e/*TXN_EVENT*, local*/ = TAILQ_FIRST/**/(&txn/*DB_TXN*, local*/->events/**//**/)/**//*TXN_EVENT*, local*/; e/*TXN_EVENT*, local*/ != NULL/*void**//*int*/; e/*TXN_EVENT*, local*/ = next_e/*TXN_EVENT*, local*//*TXN_EVENT*, local*/) {
		next_e/*TXN_EVENT*, local*/ = TAILQ_NEXT/**/(e/*TXN_EVENT*, local*/, links/**/)/**//*TXN_EVENT*, local*/;
		if ((e/*TXN_EVENT*, local*/->op/**/ != TXN_TRADE/**//*int*/ && e/*TXN_EVENT*, local*/->op/**/ != TXN_TRADED/**//*int*//*int*/)/*int*/ ||
		    (e/*TXN_EVENT*, local*/->u/**/.t/**/.lock/**/.off/**/ != lock/*DB_LOCK*, local*/->off/**//*int*/ && e/*TXN_EVENT*, local*/->u/**/.t/**/.locker/**/ != locker/*DB_LOCKER*, local*//*int*//*int*/)/*int*//*int*/)
			continue;
		TAILQ_REMOVE/**/(&txn/*DB_TXN*, local*/->events/**//**/, e/*TXN_EVENT*, local*/, links/**/)/**/;
		__os_free/**/(env/*ENV*, local*/, e/*TXN_EVENT*, local*/)/**/;
	}

	return;
}

/*
 * __txn_doevents --
 * Process the list of events associated with a transaction.  On commit,
 * apply the events; on abort, just toss the entries.
 *
 * PUBLIC: int __txn_doevents __P((ENV *, DB_TXN *, int, int));
 */

/*
 * Trade a locker associated with a thread for one that is associated
 * only with the handle. Mark the locker so failcheck will know.
 */
#define	DO_TRADE do {							\
	memset(&req, 0, sizeof(req));					\
	req.lock = e->u.t.lock;						\
	req.op = DB_LOCK_TRADE;						\
	t_ret = __lock_vec(env, txn->parent ?				\
	    txn->parent->locker : e->u.t.locker, 0, &req, 1, NULL);	\
	if (t_ret == 0)	{						\
		if (txn->parent != NULL) {				\
			e->u.t.dbp->cur_txn = txn->parent;		\
			e->u.t.dbp->cur_locker = txn->parent->locker;	\
		} else {						\
			e->op = TXN_TRADED;				\
			e->u.t.dbp->cur_locker = e->u.t.locker;		\
			F_SET(e->u.t.dbp->cur_locker,			\
			    DB_LOCKER_HANDLE_LOCKER);			\
			if (opcode != TXN_PREPARE)			\
				e->u.t.dbp->cur_txn = NULL;		\
		}							\
	} else if (t_ret == DB_NOTFOUND)				\
		t_ret = 0;						\
	if (t_ret != 0 && ret == 0)					\
		ret = t_ret;						\
} while (0)

int
__txn_doevents(env, txn, opcode, preprocess)
	ENV *env;
	DB_TXN *txn;
	int opcode, preprocess;
{
	DB_LOCKREQ req;
	TXN_EVENT *e, *enext;
	int ret, t_ret;

	ret/*int, local*/ = 0/*int*//*int, local*/;

	/*
	 * This phase only gets called if we have a phase where we
	 * release read locks.  Since not all paths will call this
	 * phase, we have to check for it below as well.  So, when
	 * we do the trade, we update the opcode of the entry so that
	 * we don't try the trade again.
	 */
	if (preprocess/*int, local*/) {
		for (e/*TXN_EVENT*, local*/ = TAILQ_FIRST/**/(&txn/*DB_TXN*, local*/->events/**//**/)/**//*TXN_EVENT*, local*/;
		    e/*TXN_EVENT*, local*/ != NULL/*void**//*int*/; e/*TXN_EVENT*, local*/ = enext/*TXN_EVENT*, local*//*TXN_EVENT*, local*/) {
			enext/*TXN_EVENT*, local*/ = TAILQ_NEXT/**/(e/*TXN_EVENT*, local*/, links/**/)/**//*TXN_EVENT*, local*/;
			if (e/*TXN_EVENT*, local*/->op/**/ != TXN_TRADE/**//*int*/ ||
			    IS_WRITELOCK/**/(e/*TXN_EVENT*, local*/->u/**/.t/**/.lock/**/.mode/**/)/**//*int*/)
				continue;
			DO_TRADE/**/;
			if (txn/*DB_TXN*, local*/->parent/**/ != NULL/*void**//*int*/) {
				TAILQ_REMOVE/**/(&txn/*DB_TXN*, local*/->events/**//**/, e/*TXN_EVENT*, local*/, links/**/)/**/;
				TAILQ_INSERT_HEAD/**/(
				     &txn/*DB_TXN*, local*/->parent/**/->events/**//**/, e/*TXN_EVENT*, local*/, links/**/)/**/;
			}
		}
		return (ret/*int, local*/)/*int, local*/;
	}

	/*
	 * Prepare should only cause a preprocess, since the transaction
	 * isn't over.
	 */
	DB_ASSERT/**/(env/*ENV*, local*/, opcode/*int, local*/ != TXN_PREPARE/**//*int*/)/**/;
	while ((e/*TXN_EVENT*, local*/ = TAILQ_FIRST/**/(&txn/*DB_TXN*, local*/->events/**//**/)/**//*TXN_EVENT*, local*/)/*TXN_EVENT*, local*/ != NULL/*void**//*int*/) {
		TAILQ_REMOVE/**/(&txn/*DB_TXN*, local*/->events/**//**/, e/*TXN_EVENT*, local*/, links/**/)/**/;
		/*
		 * Most deferred events should only happen on
		 * commits, not aborts or prepares.  The one exception
		 * is a close which gets done on commit and abort, but
		 * not prepare. If we're not doing operations, then we
		 * can just go free resources.
		 */
		if (opcode/*int, local*/ == TXN_ABORT/**//*int*/ && e/*TXN_EVENT*, local*/->op/**/ != TXN_CLOSE/**//*int*//*int*/)
			goto dofree;
		switch (e/*TXN_EVENT*, local*/->op/**/) {
		case TXN_CLOSE/**/:
			if ((t_ret/*int, local*/ = __db_close/**/(e/*TXN_EVENT*, local*/->u/**/.c/**/.dbp/**/,
			    NULL/*void**/, DB_NOSYNC/**/)/**//*int, local*/)/*int, local*/ != 0/*int*//*int*/ && ret/*int, local*/ == 0/*int*//*int*//*int*/)
				ret/*int, local*/ = t_ret/*int, local*//*int, local*/;
			break;
		case TXN_REMOVE/**/:
			if (txn/*DB_TXN*, local*/->parent/**/ != NULL/*void**//*int*/)
				TAILQ_INSERT_TAIL/**/(
				    &txn/*DB_TXN*, local*/->parent/**/->events/**//**/, e/*TXN_EVENT*, local*/, links/**/)/**/;
			else if (e/*TXN_EVENT*, local*/->u/**/.r/**/.fileid/**/ != NULL/*void**//*int*/) {
				if ((t_ret/*int, local*/ = __memp_nameop/**/(env/*ENV*, local*/,
				    e/*TXN_EVENT*, local*/->u/**/.r/**/.fileid/**/, NULL/*void**/, e/*TXN_EVENT*, local*/->u/**/.r/**/.name/**/,
				    NULL/*void**/, e/*TXN_EVENT*, local*/->u/**/.r/**/.inmem/**/)/**//*int, local*/)/*int, local*/ != 0/*int*//*int*/ && ret/*int, local*/ == 0/*int*//*int*//*int*/)
					ret/*int, local*/ = t_ret/*int, local*//*int, local*/;
			} else if ((t_ret/*int, local*/ =
			    __os_unlink/**/(env/*ENV*, local*/, e/*TXN_EVENT*, local*/->u/**/.r/**/.name/**/, 0/*int*/)/**//*int, local*/)/*int, local*/ != 0/*int*//*int*/ && ret/*int, local*/ == 0/*int*//*int*//*int*/)
				ret/*int, local*/ = t_ret/*int, local*//*int, local*/;
			break;
		case TXN_TRADE/**/:
			DO_TRADE/**/;
			if (txn/*DB_TXN*, local*/->parent/**/ != NULL/*void**//*int*/) {
				TAILQ_INSERT_HEAD/**/(
				     &txn/*DB_TXN*, local*/->parent/**/->events/**//**/, e/*TXN_EVENT*, local*/, links/**/)/**/;
				continue;
			}
			/* Fall through */
		case TXN_TRADED/**/:
			/* Downgrade the lock. */
			if ((t_ret/*int, local*/ = __lock_downgrade/**/(env/*ENV*, local*/,
			    &e/*TXN_EVENT*, local*/->u/**/.t/**/.lock/**//**/, DB_LOCK_READ/**/, 0/*int*/)/**//*int, local*/)/*int, local*/ != 0/*int*//*int*/ && ret/*int, local*/ == 0/*int*//*int*//*int*/)
				ret/*int, local*/ = t_ret/*int, local*//*int, local*/;
			break;
		default:
			/* This had better never happen. */
			DB_ASSERT/**/(env/*ENV*, local*/, 0/*int*/)/**/;
		}
dofree:
		/* Free resources here. */
		switch (e/*TXN_EVENT*, local*/->op/**/) {
		case TXN_REMOVE/**/:
			if (txn/*DB_TXN*, local*/->parent/**/ != NULL/*void**//*int*/)
				continue;
			if (e/*TXN_EVENT*, local*/->u/**/.r/**/.fileid/**/ != NULL/*void**//*int*/)
				__os_free/**/(env/*ENV*, local*/, e/*TXN_EVENT*, local*/->u/**/.r/**/.fileid/**/)/**/;
			__os_free/**/(env/*ENV*, local*/, e/*TXN_EVENT*, local*/->u/**/.r/**/.name/**/)/**/;
			break;
		case TXN_TRADE/**/:
			if (opcode/*int, local*/ == TXN_ABORT/**//*int*/)
				e/*TXN_EVENT*, local*/->u/**/.t/**/.dbp/**/->cur_txn/**/ = NULL/*void**//*void**/;
			break;
		case TXN_CLOSE/**/:
		case TXN_TRADED/**/:
		default:
			break;
		}
		__os_free/**/(env/*ENV*, local*/, e/*TXN_EVENT*, local*/)/**/;
	}

	return (ret/*int, local*/)/*int, local*/;
}

/*
 * PUBLIC: int __txn_record_fname __P((ENV *, DB_TXN *, FNAME *));
 */
int
__txn_record_fname(env, txn, fname)
	ENV *env;
	DB_TXN *txn;
	FNAME *fname;
{
	DB_LOG *dblp;
	DB_TXNMGR *mgr;
	TXN_DETAIL *td;
	roff_t fname_off;
	roff_t *np, *ldbs;
	u_int32_t i;
	int ret;

	if ((td/*TXN_DETAIL*, local*/ = txn/*DB_TXN*, local*/->td/**//*TXN_DETAIL*, local*/)/*TXN_DETAIL*, local*/ == NULL/*void**//*int*/)
		return (0/*int*/)/*int*/;
	mgr/*DB_TXNMGR*, local*/ = env/*ENV*, local*/->tx_handle/**//*DB_TXNMGR*, local*/;
	dblp/*DB_LOG*, local*/ = env/*ENV*, local*/->lg_handle/**//*DB_LOG*, local*/;
	fname_off/*roff_t, local*/ = R_OFFSET/**/(&dblp/*DB_LOG*, local*/->reginfo/**//**/, fname/*FNAME*, local*/)/**//*roff_t, local*/;

	/* See if we already have a ref to this DB handle. */
	ldbs/*roff_t*, local*/ = R_ADDR/**/(&mgr/*DB_TXNMGR*, local*/->reginfo/**//**/, td/*TXN_DETAIL*, local*/->log_dbs/**/)/**//*roff_t*, local*/;
	for (i/*u_int32_t, local*/ = 0/*int*//*u_int32_t, local*/, np/*roff_t*, local*/ = ldbs/*roff_t*, local*//*roff_t*, local*//*roff_t*, local*/; i/*u_int32_t, local*/ < td/*TXN_DETAIL*, local*/->nlog_dbs/**//*int*/; i/*u_int32_t, local*/++/*u_int32_t, local*/, np/*roff_t*, local*/++/*roff_t*, local*//*roff_t*, local*/)
		if (*np/*roff_t*, local*//*roff_t*/ == fname_off/*roff_t, local*//*int*/)
			return (0/*int*/)/*int*/;

	if (td/*TXN_DETAIL*, local*/->nlog_slots/**/ <= td/*TXN_DETAIL*, local*/->nlog_dbs/**//*int*/) {
		TXN_SYSTEM_LOCK/**/(env/*ENV*, local*/)/**/;
		if ((ret/*int, local*/ = __env_alloc/**/(&mgr/*DB_TXNMGR*, local*/->reginfo/**//**/,
		    sizeof(roff_t)/*size_t*/ * (td/*TXN_DETAIL*, local*/->nlog_slots/**/ << 1/*int*//*int*/)/*int*//*size_t*/, &np/*roff_t*, local*//*roff_t***/)/**//*int, local*/)/*int, local*/ != 0/*int*//*int*/) {
			TXN_SYSTEM_UNLOCK/**/(env/*ENV*, local*/)/**/;
			return (ret/*int, local*/)/*int, local*/;
		}

		memcpy/**/(np/*roff_t*, local*/, ldbs/*roff_t*, local*/, td/*TXN_DETAIL*, local*/->nlog_dbs/**/ * sizeof(roff_t)/*size_t*//*size_t*/)/**/;
		if (td/*TXN_DETAIL*, local*/->nlog_slots/**/ > TXN_NSLOTS/**//*int*/)
			__env_alloc_free/**/(&mgr/*DB_TXNMGR*, local*/->reginfo/**//**/, ldbs/*roff_t*, local*/)/**/;

		TXN_SYSTEM_UNLOCK/**/(env/*ENV*, local*/)/**/;
		td/*TXN_DETAIL*, local*/->log_dbs/**/ = R_OFFSET/**/(&mgr/*DB_TXNMGR*, local*/->reginfo/**//**/, np/*roff_t*, local*/)/**//**/;
		ldbs/*roff_t*, local*/ = np/*roff_t*, local*//*roff_t*, local*/;
		td/*TXN_DETAIL*, local*/->nlog_slots/**/ = td/*TXN_DETAIL*, local*/->nlog_slots/**/ << 1/*int*//*int*//*int*/;
	}

	ldbs/*roff_t*, local*/[td/*TXN_DETAIL*, local*/->nlog_dbs/**/]/*roff_t*/ = fname_off/*roff_t, local*//*roff_t*/;
	td/*TXN_DETAIL*, local*/->nlog_dbs/**/++/**/;
	fname/*FNAME*, local*/->txn_ref/**/++/**/;

	return (0/*int*/)/*int*/;
}

/*
 * __txn_dref_fnam --
 *	Either pass the fname to our parent txn or decrement the refcount
 * and close the fileid if it goes to zero.
 *
 * PUBLIC: int __txn_dref_fname __P((ENV *, DB_TXN *));
 */
int
__txn_dref_fname(env, txn)
	ENV *env;
	DB_TXN *txn;
{
	DB_LOG *dblp;
	DB_TXNMGR *mgr;
	FNAME *fname;
	roff_t *np;
	TXN_DETAIL *ptd, *td;
	u_int32_t i;
	int ret;

	td/*TXN_DETAIL*, local*/ = txn/*DB_TXN*, local*/->td/**//*TXN_DETAIL*, local*/;

	if (td/*TXN_DETAIL*, local*/->nlog_dbs/**/ == 0/*int*//*int*/)
		return (0/*int*/)/*int*/;

	mgr/*DB_TXNMGR*, local*/ = env/*ENV*, local*/->tx_handle/**//*DB_TXNMGR*, local*/;
	dblp/*DB_LOG*, local*/ = env/*ENV*, local*/->lg_handle/**//*DB_LOG*, local*/;
	ret/*int, local*/ = 0/*int*//*int, local*/;

	ptd/*TXN_DETAIL*, local*/ = txn/*DB_TXN*, local*/->parent/**/ != NULL/*void**//*int*/ ? txn/*DB_TXN*, local*/->parent/**/->td/**/ : NULL/*void**//*void**//*TXN_DETAIL*, local*/;

	np/*roff_t*, local*/ = R_ADDR/**/(&mgr/*DB_TXNMGR*, local*/->reginfo/**//**/, td/*TXN_DETAIL*, local*/->log_dbs/**/)/**//*roff_t*, local*/;
	np/*roff_t*, local*/ += td/*TXN_DETAIL*, local*/->nlog_dbs/**/ - 1/*int*//*int*//*roff_t*, local*/;
	void f() { // jll
		MUTEX_LOCK/**/(env/*ENV*, local*/, fname/*FNAME*, local*/->mutex/**/)/**/;
		if (ptd/*TXN_DETAIL*, local*/ != NULL/*void**//*int*/) {
			ret/*int, local*/ = __txn_record_fname/**/(env/*ENV*, local*/, txn/*DB_TXN*, local*/->parent/**/, fname/*FNAME*, local*/)/**//*int, local*/;
			fname/*FNAME*, local*/->txn_ref/**/--/**/;
		} else if (fname/*FNAME*, local*/->txn_ref/**/ == 1/*int*//*int*/) {
			MUTEX_UNLOCK/**/(env/*ENV*, local*/, fname/*FNAME*, local*/->mutex/**/)/**/;
			DB_ASSERT/**/(env/*ENV*, local*/, fname/*FNAME*, local*/->txn_ref/**/ != 0/*int*//*int*/)/**/;
			ret/*int, local*/ = __dbreg_close_id_int/**/(
			    env/*ENV*, local*/, fname/*FNAME*, local*/, DBREG_CLOSE/**/, 0/*int*/)/**//*int, local*/;
			return;
		} else {
			fname/*FNAME*, local*/->txn_ref/**/--/**/;
		}
		MUTEX_UNLOCK/**/(env/*ENV*, local*/, fname/*FNAME*, local*/->mutex/**/)/**/;
	}

	for (i/*u_int32_t, local*/ = 0/*int*//*u_int32_t, local*/; i/*u_int32_t, local*/ < td/*TXN_DETAIL*, local*/->nlog_dbs/**//*int*/; i/*u_int32_t, local*/++/*u_int32_t, local*/, np/*roff_t*, local*/--/*roff_t*, local*//*roff_t*, local*/) {
		fname/*FNAME*, local*/ = R_ADDR/**/(&dblp/*DB_LOG*, local*/->reginfo/**//**/, *np/*roff_t*, local*//*roff_t*/)/**//*FNAME*, local*/;
		f/**/()/**/; // jll
		if (ret/*int, local*/ != 0/*int*//*int*/ && ret/*int, local*/ != EIO/**//*int*//*int*/)
			break;
	}

	return (ret/*int, local*/)/*int, local*/;
}

/*
 * Common removal routine.  This is called only after verifying that
 * the DB_MPOOLFILE is in the list.
 */
static void
__clear_fe_watermark(txn, db)
     DB_TXN *txn;
     DB *db;
{
	MPOOLFILE *mpf;

	mpf/*MPOOLFILE*, local*/ = db/*DB*, local*/->mpf/**/->mfp/**//*MPOOLFILE*, local*/;
	mpf/*MPOOLFILE*, local*/->fe_watermark/**/ = PGNO_INVALID/**//**/;
	mpf/*MPOOLFILE*, local*/->fe_txnid/**/ = 0U/*unsigned int*//*unsigned int*/;
	mpf/*MPOOLFILE*, local*/->fe_nlws/**/ = 0U/*unsigned int*//*unsigned int*/;
	TAILQ_REMOVE/**/(&txn/*DB_TXN*, local*/->femfs/**//**/, db/*DB*, local*/, felink/**/)/**/;
}

/*
 * __txn_reset_fe_watermarks
 * Reset the file extension state of MPOOLFILEs involved in this transaction.
 *
 * PUBLIC: void __txn_reset_fe_watermarks __P((DB_TXN *));
 */
void
__txn_reset_fe_watermarks(txn)
     DB_TXN *txn;
{
	DB *db;

	if (txn/*DB_TXN*, local*/->parent/**/) {
		DB_ASSERT/**/(txn/*DB_TXN*, local*/->mgrp/**/->env/**/, TAILQ_FIRST/**/(&txn/*DB_TXN*, local*/->femfs/**//**/)/**/ == NULL/*void**//*int*/)/**/;
	}

	while ((db/*DB*, local*/ = TAILQ_FIRST/**/(&txn/*DB_TXN*, local*/->femfs/**//**/)/**//*DB*, local*/)/*DB*, local*/)
		__clear_fe_watermark/**/(txn/*DB_TXN*, local*/, db/*DB*, local*/)/**/;
}

/*
 * __txn_remove_fe_watermark
 * Remove a watermark from the transaction's list
 *
 * PUBLIC: void __txn_remove_fe_watermark __P((DB_TXN *,DB *));
 */
void
__txn_remove_fe_watermark(txn, db)
     DB_TXN *txn;
     DB *db;
{
	DB *db_tmp;

	if (txn/*DB_TXN*, local*/ == NULL/*void**//*int*/ || !F_ISSET/**/(txn/*DB_TXN*, local*/, TXN_BULK/**/)/**//*int*//*int*/)
		return;

	TAILQ_FOREACH(db_tmp/*DB*, local*/, &txn/*DB_TXN*, local*/->femfs/**//**/, felink/**/) {
		if (db_tmp/*DB*, local*/ == db/*DB*, local*//*int*/) {
			__clear_fe_watermark/**/(txn/*DB_TXN*, local*/, db/*DB*, local*/)/**/;
			break;
		}
	}
}

/*
 * __txn_add_fe_watermark
 *
 * Add an entry to the transaction's list of
 * file_extension_watermarks, if warranted.  Also, set the watermark
 * page number in the MPOOLFILE.  The metadata lock associated with
 * the mfp must be held when this function is called.
 *
 * PUBLIC: void __txn_add_fe_watermark __P((DB_TXN *, DB *, db_pgno_t));
 */
void
__txn_add_fe_watermark(txn, db, pgno)
     DB_TXN *txn;
     DB *db;
     db_pgno_t pgno;
{
	MPOOLFILE *mfp;

	if (txn/*DB_TXN*, local*/ == NULL/*void**//*int*/ || !F_ISSET/**/(txn/*DB_TXN*, local*/, TXN_BULK/**/)/**//*int*//*int*/)
		return;

	mfp/*MPOOLFILE*, local*/ = db/*DB*, local*/->mpf/**/->mfp/**//*MPOOLFILE*, local*/;
	/* If the watermark is already set, there's nothing to do. */
	if (mfp/*MPOOLFILE*, local*/->fe_watermark/**/ != PGNO_INVALID/**//*int*/) {
#ifdef DIAGNOSTIC
		DB_ASSERT/**/(txn/*DB_TXN*, local*/->mgrp/**/->env/**/, mfp/*MPOOLFILE*, local*/->fe_txnid/**/ == txn/*DB_TXN*, local*/->txnid/**//*int*/)/**/;
#endif
		return;
	}

	/* We can update MPOOLFILE because the metadata lock is held. */
	mfp/*MPOOLFILE*, local*/->fe_watermark/**/ = pgno/*db_pgno_t, local*//*db_pgno_t, local*/;
	mfp/*MPOOLFILE*, local*/->fe_txnid/**/ = txn/*DB_TXN*, local*/->txnid/**//**/;

	TAILQ_INSERT_TAIL/**/(&txn/*DB_TXN*, local*/->femfs/**//**/, db/*DB*, local*/, felink/**/)/**/;
}

/*
 * __txn_flush_fe_files
 * For every extended file in which a log record write was skipped,
 * flush the data pages.  This is called during commit.
 *
 * PUBLIC: int __txn_flush_fe_files __P((DB_TXN *));
 */
int
__txn_flush_fe_files(txn)
     DB_TXN *txn;
{
	DB *db;
	ENV *env;
	int ret;

	env/*ENV*, local*/ = txn/*DB_TXN*, local*/->mgrp/**/->env/**//*ENV*, local*/;

	DB_ASSERT/**/(env/*ENV*, local*/, txn/*DB_TXN*, local*/->mgrp/**/ != NULL/*void**//*int*/)/**/;
	DB_ASSERT/**/(env/*ENV*, local*/, env/*ENV*, local*/ != NULL/*void**//*int*/)/**/;

#ifdef DIAGNOSTIC
	DB_ASSERT/**/(env/*ENV*, local*/, txn/*DB_TXN*, local*/->parent/**/ == NULL/*void**//*int*/)/**/;
#endif

	TAILQ_FOREACH(db/*DB*, local*/, &txn/*DB_TXN*, local*/->femfs/**//**/, felink/**/) {
		if (db/*DB*, local*/->mpf/**/->mfp/**/->fe_nlws/**/ > 0/*int*//*int*/ &&
		    (ret/*int, local*/ = __memp_sync_int/**/(env/*ENV*, local*/, db/*DB*, local*/->mpf/**/, 0/*int*/,
		    DB_SYNC_FILE/**/, NULL/*void**/, NULL/*void**/)/**//*int, local*/)/*int, local*//*int*/)
			return (ret/*int, local*/)/*int, local*/;
	}

	return (0/*int*/)/*int*/;
}

/*
 * __txn_pg_above_fe_watermark --
 *
 * Test whether there is a file extension watermark for the given
 * database, and, if so, whether the given page number is above the
 * watermark.  If this test returns true, then logging of the page's
 * update can be suppressed when the file extension/bulk loading
 * optimization is in force.
 *
 * PUBLIC: int __txn_pg_above_fe_watermark
 * PUBLIC:	__P((DB_TXN*, MPOOLFILE*, db_pgno_t));
 */
int
__txn_pg_above_fe_watermark(txn, mpf, pgno)
     DB_TXN *txn;
     MPOOLFILE *mpf;
     db_pgno_t pgno;
{
	ENV *env;
	int skip;

	if (txn/*DB_TXN*, local*/ == NULL/*void**//*int*/ || (!F_ISSET/**/(txn/*DB_TXN*, local*/, TXN_BULK/**/)/**//*int*/)/*int*//*int*/ ||
	    mpf/*MPOOLFILE*, local*/->fe_watermark/**/ == PGNO_INVALID/**//*int*//*int*/)
		return (0/*int*/)/*int*/;

	env/*ENV*, local*/ = txn/*DB_TXN*, local*/->mgrp/**/->env/**//*ENV*, local*/;

	skip/*int, local*/ = 0/*int*//*int, local*/;
	TXN_SYSTEM_LOCK/**/(env/*ENV*, local*/)/**/;
	if (((DB_TXNREGION *)env/*ENV*, local*/->tx_handle/**/->reginfo/**/.primary/**//*DB_TXNREGION**/)/*DB_TXNREGION**/->n_hotbackup/**/ > 0/*int*//*int*/)
		skip/*int, local*/ = 1/*int*//*int, local*/;
	TXN_SYSTEM_UNLOCK/**/(env/*ENV*, local*/)/**/;
	if (skip/*int, local*/)
		return (0/*int*/)/*int*/;

	/*
	 * If the watermark is a valid page number, then the extending
	 * transaction should be the current outermost transaction.
	 */
	DB_ASSERT/**/(txn/*DB_TXN*, local*/->mgrp/**/->env/**/, mpf/*MPOOLFILE*, local*/->fe_txnid/**/ == txn/*DB_TXN*, local*/->txnid/**//*int*/)/**/;

	return (mpf/*MPOOLFILE*, local*/->fe_watermark/**/ <= pgno/*db_pgno_t, local*//*int*/)/*int*/;
}
