/*
 * Copyright (c) 2003-2005 Sendmail, Inc. and its suppliers.
 *      All rights reserved.
 *
 * By using this file, you agree to the terms and conditions set
 * forth in the LICENSE file which can be found at the top level of
 * the sendmail distribution.
 */

#include "sm/generic.h"
SM_RCSID("@(#)$Id: rdibdb.c,v 1.74 2007/06/18 04:42:31 ca Exp $")
#include "sm/error.h"
#include "sm/magic.h"
#include "sm/assert.h"
#include "sm/memops.h"
#include "sm/ibdb.h"
#include "sm/qmgr.h"
#include "sm/qmgr-int.h"
#include "sm/bhtable.h"
#include "sm/rdibdb.h"
#include "sm/qmibdb.h"
#include "liblog.h"

#if SM_TEST_PRT
#include "sm/io.h"
#endif

/* HACK (used by test programs that include this file) */
#define BHTSIZE		(32 * 1024)

/*
**  Problems:
**  time isn't directly available: file mtime could be used,
**	for now: current time.
*/

/*
**  QM_IBDB_TA_CLEAN -- Clean out IBDB TA structure for reuse
**	Reset values in IBDB TA to be used again for ibdbr_get()
**	(which will create cdb_id itself).
**
**	Parameters:
**		ibdb_ta -- IBDB TA
**
**	Returns:
**		none.
*/

void
qm_ibdb_ta_clean(ibdb_ta_P ibdb_ta)
{
	SM_REQUIRE(ibdb_ta);
	SM_CSTR_FREE(ibdb_ta->ibt_cdb_id);
}

/* context for ta_cancel */
typedef struct tac_ctx_S	tac_ctx_T, *tac_ctx_P;
struct tac_ctx_S
{
#if SM_TAC_CTX_CHECK
	sm_magic_T	 sm_magic;
#endif
	bht_P		 tacx_bht_rcpt;	/* bhtable RCPT */
	char		*tacx_ta_id;
};

#if SM_TAC_CTX_CHECK
# define SM_IS_TAC_CTX(tac_ctx)	SM_REQUIRE_ISA((tac_ctx), SM_TAC_CTX_MAGIC)
#else
# define SM_IS_TAC_CTX(tac_ctx)	SM_REQUIRE((tac_ctx) != NULL)
#endif

/*
**  TA_CANCEL -- Cancel all recipients of a transaction (callback function)
**
**	Parameters:
**		info -- bht entry
**		ctx -- TA ID
**
**	Returns:
**		usual sm_error code
*/

static sm_ret_T
ta_cancel_rcpts(bht_entry_P info, void *ctx)
{
	ibdb_rcpt_P ibdb_rcpt;
	sessta_id_P ta_id;
	tac_ctx_P tac_ctx;

	SM_REQUIRE(info != NULL);
	SM_REQUIRE(ctx != NULL);
	ibdb_rcpt = (ibdb_rcpt_P) info->bhe_value;
	SM_REQUIRE(ibdb_rcpt->ibr_ta_id != NULL);
	tac_ctx = (tac_ctx_P) ctx;
	SM_IS_TAC_CTX(tac_ctx);
	ta_id = tac_ctx->tacx_ta_id;
	if (SESSTA_EQ(ibdb_rcpt->ibr_ta_id, ta_id))
	{
		char rcpt_id[SMTP_RCPTID_SIZE + 1];

#if SM_TEST_PRT
		if (debug > 1)
		{
			sm_io_fprintf(smioout, "cancel TA: ta=%s, ", ta_id);
			sm_io_fprintf(smioout, "rcpt_idx=%d, ",
				ibdb_rcpt->ibr_idx);
			sm_io_fprintf(smioout, "\n");
		}
#endif /* SM_TEST_PRT */

		sm_snprintf(rcpt_id, SMTP_RCPTID_SIZE, SMTP_RCPTID_FORMAT,
			ibdb_rcpt->ibr_ta_id, ibdb_rcpt->ibr_idx);
		bht_rm(tac_ctx->tacx_bht_rcpt, rcpt_id, SMTP_RCPTID_SIZE,
			ibdb_rcpt_free, NULL);
	}
	return SM_SUCCESS;
}

/*
**  QM_IBDB_CANCEL_TA -- Cancel a transaction
**
**	Parameters:
**		bht_ta -- bhtable (TA)
**		bht_rcpt -- bhtable (RCPT)
**		ta_id -- TA ID
**
**	Returns:
**		usual sm_error code
**
**	Note: unfortunately it is required to "walk" through the entire
**		recipient hash table and find matching entries because the
**		key is rcpt_id, not ta_id, hence lookups of ta_id can't
**		find anything.  Even if bhtable would have a "lookup sub key"
**		function, it wouldn't work to find all matching entries:
**		due to the hashing they are (hopefully) spread over the table.
**		a different map type (btree) would allow for a faster access
**		method (maybe use BDB btree, in memory only?).
*/

sm_ret_T
qm_ibdb_cancel_ta(bht_P bht_ta, bht_P bht_rcpt, sessta_id_P ta_id)
{
	tac_ctx_T tac_ctx;

	tac_ctx.tacx_bht_rcpt = bht_rcpt;
	tac_ctx.tacx_ta_id = ta_id;
	(void) bht_walk(bht_rcpt, ta_cancel_rcpts, (void *) &tac_ctx);
	(void) bht_rm(bht_ta, ta_id, SMTP_STID_SIZE, ibdb_ta_free, NULL);
	return SM_SUCCESS;
}

/*
**  QM_IBDB_TA_ACTION -- Add transaction to DEFEDB (callback function)
**
**	Parameters:
**		info -- bht entry
**		ctx -- context
**
**	Returns:
**		usual sm_error code
*/

static sm_ret_T
qm_ibdb_ta_action(bht_entry_P info, void *ctx)
{
	ibdb_ta_P ibdb_ta;
	ri_ctx_P ri_ctx;
	aq_ta_P aq_ta;
#if !SM_TEST_PRT
	sm_ret_T ret;
	qmgr_ctx_P qmgr_ctx;
	edb_req_P edb_req;
#endif /* !SM_TEST_PRT */

	/*
	**  Do NOT return an error code because that will stop
	**  the recovery (bht_walk()).
	*/

	ri_ctx = (ri_ctx_P) ctx;
	ibdb_ta = (ibdb_ta_P) info->bhe_value;
	if (ibdb_ta->ibt_ta_id == NULL)
		return SM_SUCCESS;
		/* sm_error_perm(SM_EM_Q_RDIBDB, SM_E_UNEXPECTED); */

#if !SM_TEST_PRT
	/* check whether TA already in DEFEDB */
	qmgr_ctx = ri_ctx->rix_qmgr_ctx;
	if (qmgr_ctx != NULL)
	{
		SM_IS_QMGR_CTX(qmgr_ctx);
		edb_req = ri_ctx->rix_edb_req;
		SESSTA_COPY(edb_req->edb_req_id, ibdb_ta->ibt_ta_id);
		edb_req->edb_req_type = EDB_REQ_TA;
		ret = edb_rd_req(qmgr_ctx->qmgr_edb, edb_req);
		if (ret == SM_SUCCESS)
		{
			/* the data in EDB is ok, no need to change it */
			return ret;
		}
	}
#endif /* !SM_TEST_PRT */

	aq_ta = ri_ctx->rix_aq_ta;
	aq_ta->aqt_st_time = ri_ctx->rix_time;
	aq_ta->aqt_rcpts_inaq = ibdb_ta->ibt_nrcpts;
	aq_ta->aqt_rcpts_tot = ibdb_ta->ibt_nrcpts;

	aq_ta->aqt_nxt_idx = ibdb_ta->ibt_nrcpts;

	aq_ta->aqt_rcpts_tot = ibdb_ta->ibt_nrcpts;
	aq_ta->aqt_rcpts_left = ibdb_ta->ibt_rcpts_left;
	aq_ta->aqt_rcpts_temp = ibdb_ta->ibt_rcpts_temp;
	aq_ta->aqt_rcpts_perm = ibdb_ta->ibt_rcpts_perm;
	aq_ta->aqt_rcpts_tried = ibdb_ta->ibt_nrcpts - ibdb_ta->ibt_rcpts_left;

	SESSTA_COPY(aq_ta->aqt_ss_ta_id, ibdb_ta->ibt_ta_id);
	aq_ta->aqt_cdb_id = ibdb_ta->ibt_cdb_id;
	/* who is responsible for CDB ID? should we use CSTR_DUP? */

	aq_ta->aqt_mail->aqm_pa = ibdb_ta->ibt_mail_pa;

	/* "transfer" header of list to aq_ta */
	aq_ta->aqt_hdrmodhd = ibdb_ta->ibt_hdrmodhd;
	ibdb_ta->ibt_hdrmodhd = NULL;

#if !SM_TEST_PRT
	if (qmgr_ctx != NULL)
	{
		/* Which status?? */
		ret = edb_ta_app(qmgr_ctx->qmgr_edb, aq_ta,
				&ri_ctx->rix_edb_req_hd, 0 /* XXX */);
		if (sm_is_err(ret))
			QM_LEV_DPRINTFC(QDC_IBDBR, 0, (QM_DEBFP,
				"sev=ERROR, func=qm_ibdb_ta_action, ss_ta=%s, edb_ta_app=%m\n",
				ibdb_ta->ibt_ta_id, ret));
	}
#endif /* !SM_TEST_PRT */

#if SM_TEST_PRT
	if (debug > 1)
	{
		sm_io_fprintf(smioout, "TA: ta=%s, ", ibdb_ta->ibt_ta_id);
		sm_io_fprintf(smioout, "cdb=%C, ", ibdb_ta->ibt_cdb_id);
		sm_io_fprintf(smioout, "mail_pa=%S, ", aq_ta->aqt_mail->aqm_pa);
		sm_io_fprintf(smioout, "rcpts_tot=%d, ", aq_ta->aqt_rcpts_tot);
		sm_io_fprintf(smioout, "rcpts_left=%d, ", aq_ta->aqt_rcpts_left);
		sm_io_fprintf(smioout, "\n");
	}
#endif /* SM_TEST_PRT */

	aq_ta->aqt_mail->aqm_pa = NULL;
	SM_STR_FREE(ibdb_ta->ibt_mail_pa);
	return SM_SUCCESS;
}

/*
**  QM_IBDB_RCPT_ACTION -- Add recipient to DEFEDB (callback function)
**
**	Parameters:
**		info -- bht entry
**		ctx -- context
**
**	Returns:
**		usual sm_error code
*/

static sm_ret_T
qm_ibdb_rcpt_action(bht_entry_P info, void *ctx)
{
	sm_ret_T ret;
	ibdb_rcpt_P ibdb_rcpt;
	ibdb_ta_P ibdb_ta;
	ri_ctx_P ri_ctx;
	aq_ta_P aq_ta;
	aq_rcpt_P aq_rcpt;
	qmgr_ctx_P qmgr_ctx;
	edb_req_P edb_req;

	/*
	**  Do NOT return an error code because that will stop
	**  the recovery (bht_walk()).
	*/

	ri_ctx = (ri_ctx_P) ctx;
	ibdb_rcpt = (ibdb_rcpt_P) info->bhe_value;
	if (ibdb_rcpt->ibr_ta_id == NULL)
		return SM_SUCCESS; /* sm_error_perm(SM_EM_Q_RDIBDB, SM_E_UNEXPECTED); */

	aq_ta = ri_ctx->rix_aq_ta;
	aq_rcpt = ri_ctx->rix_aq_rcpt;

	ibdb_ta = bht_find(ri_ctx->rix_bht_ta, ibdb_rcpt->ibr_ta_id,
			SMTP_STID_SIZE);
	if (ibdb_ta == NULL)
	{
		/*
		**  XXX OOPS what's going on? Where's our TA?
		**  Under which circumstances can this happen?
		*/


QM_LEV_DPRINTFC(QDC_IBDBR, 0, (QM_DEBFP, "ERROR: bht_find(%s) failed\n", ibdb_rcpt->ibr_ta_id));
#if QM_DEBUG_RDIBDB
		/* let's crash ... */
		SM_ASSERT(ibdb_ta != NULL);
#endif /* QM_DEBUG_RDIBDB */
		return SM_SUCCESS; /* sm_error_perm(SM_EM_Q_RDIBDB, SM_E_NOTFOUND); */
	}

#if !SM_TEST_PRT
	/* check whether rcpt already in DEFEDB */
	qmgr_ctx = ri_ctx->rix_qmgr_ctx;
	if (qmgr_ctx != NULL)
	{
		SM_IS_QMGR_CTX(qmgr_ctx);
		edb_req = ri_ctx->rix_edb_req;
		RCPT_ID_COPY(edb_req->edb_req_id, info->bhe_key);
		edb_req->edb_req_type = EDB_REQ_RCPT;
		ret = edb_rd_req(qmgr_ctx->qmgr_edb, edb_req);
		/* found: ignore it (XXX not even counting?) */
		if (ret == SM_SUCCESS)
			return ret;
	}
#endif /* !SM_TEST_PRT */

	ibdb_ta->ibt_rcpts_left++;

	/* Let's copy it over... */
	SESSTA_COPY(aq_rcpt->aqr_ss_ta_id, ibdb_rcpt->ibr_ta_id);
	aq_rcpt->aqr_pa = ibdb_rcpt->ibr_pa;
	aq_rcpt->aqr_da_idx = 0;
	aq_rcpt->aqr_st_time = ri_ctx->rix_time;
	aq_rcpt->aqr_entered = ri_ctx->rix_time;
	aq_rcpt->aqr_idx = ibdb_rcpt->ibr_idx;
	aq_rcpt->aqr_status = AQR_ST_NEW;
	AQR_DA_INIT(aq_rcpt);
	AQR_SS_INIT(aq_rcpt);
#if !SM_TEST_PRT
	if (qmgr_ctx != NULL)
	{
		ret = edb_rcpt_app(qmgr_ctx->qmgr_edb, aq_rcpt,
			&ri_ctx->rix_edb_req_hd,
			aq_rcpt->aqr_status);
		if (sm_is_err(ret))
			QM_LEV_DPRINTFC(QDC_IBDBR, 0, (QM_DEBFP, "sev=ERROR, func=qm_ibdb_rcpt_action, rcpt_id=%s, edb_rcpt_app=%r\n",
				ibdb_rcpt->ibr_ta_id, ibdb_rcpt->ibr_idx,
				ret));
	}
#endif /* !SM_TEST_PRT */

#if SM_TEST_PRT
	if (debug > 1)
	{
		sm_io_fprintf(smioout, "RCPT: ta=%s, ", ibdb_rcpt->ibr_ta_id);
		sm_io_fprintf(smioout, "rcpt_pa=%S, ", aq_rcpt->aqr_pa);
		sm_io_fprintf(smioout, "rcpt_idx=%d, ", ibdb_rcpt->ibr_idx);
		sm_io_fprintf(smioout, "\n");
	}
#endif /* SM_TEST_PRT */

	aq_rcpt->aqr_pa = NULL;
	SM_STR_FREE(ibdb_rcpt->ibr_pa);
	return SM_SUCCESS;
}

/*
**  QM_RIBDB -- read INCEDB records and discard "closed" transactions.
**
**	Parameters:
**		ri_ctx -- Recover-IBDB context
**
**	Returns:
**		usual sm_error code
**
**	Input: ri_ctx contains initialized values for:
**		rix_ibdbrc
**		rix_edb_req_hd
**		rix_bht_ta
**		rix_bht_rcpt
**		rix_ntas
**		rix_nrcpts
**
**	Output: ri_ctx contains (possibly) updated values for:
**		rix_edb_req_hd
**		rix_bht_ta
**		rix_bht_rcpt
**		rix_ntas
**		rix_nrcpts
**		rix_last_id
**
**	Notes:
**		- this function reads _all_ entries into memory; it removes
**		transactions that are discarded, but keeps all open
**		transactions in memory. there should be only a few of those,
**		hence memory should be small. if this ever turns out to
**		be a problem, those entries could be written directly to
**		defedb (and removed if the transaction is closed/cancelled).
**		- currently this function calls bht_walk for TA and RCPT,
**		hence the other values in ri_ctx must be initialized too.
*/

sm_ret_T
qm_ribdb(ri_ctx_P ri_ctx)
{
	int status;
	sm_ret_T ret;
	bool more;
	id_count_T idc;
	ibdbr_ctx_P ibdbrc;
	ibdb_ta_P ibdb_ta, ibdb_lta;
	ibdb_rcpt_P ibdb_rcpt, ibdb_lrcpt;
	ibdb_hdrmod_P ibdb_hdrmod;
	sm_hdrmod_P sm_hdrmod;
	bht_P bht_ta, bht_rcpt;
	bht_entry_P bhte;
	rcpt_id_P rcpt_id;

	ibdbrc = ri_ctx->rix_ibdbrc;
	bht_ta = ri_ctx->rix_bht_ta;
	bht_rcpt = ri_ctx->rix_bht_rcpt;

	ret = qm_ibdb_ta_new(&ibdb_ta);
	if (sm_is_err(ret) || ibdb_ta == NULL)
		goto end;
	ret = qm_ibdb_rcpt_new(&ibdb_rcpt);
	if (sm_is_err(ret) || ibdb_rcpt == NULL)
		goto end;
	ret = ibdbr_hdrmod_new(&ibdb_hdrmod);
	if (sm_is_err(ret) || ibdb_hdrmod == NULL)
		goto end;

	more = true;
	do
	{
		ret = ibdbr_get(ibdbrc, ibdb_rcpt, ibdb_ta, ibdb_hdrmod
				, &status);
		switch (ret)
		{
		  case RT_IBDB_TA:
			++ri_ctx->rix_ntas;
			if (status == IBDB_TA_NEW)
			{
				ret = sm_id2idc(ibdb_ta->ibt_ta_id, &idc);
				if (ret == SM_SUCCESS &&
				    ri_ctx->rix_last_id < idc)
					ri_ctx->rix_last_id = idc;
				ret = bht_add(bht_ta,
					ibdb_ta->ibt_ta_id, SMTP_STID_SIZE,
					ibdb_ta, &bhte);
				if (sm_is_err(ret))
				{
					/* XXX what to do now? */
					QM_LEV_DPRINTFC(QDC_IBDBR, 0, (QM_DEBFP, "sev=ERROR, func=qm_ribdb, bht_add=%r, ta=%s\n", ret, ibdb_ta->ibt_ta_id));
					goto end;
				}
				ret = qm_ibdb_ta_new(&ibdb_ta);
				if (sm_is_err(ret) || ibdb_ta == NULL)
					goto end;
			}
			else if (status == IBDB_TA_CANCEL)
			{
				qm_ibdb_cancel_ta(bht_ta, bht_rcpt,
					ibdb_ta->ibt_ta_id);
				qm_ibdb_ta_clean(ibdb_ta);
			}
			else
			{
				ibdb_lta = bht_find(bht_ta,
					ibdb_ta->ibt_ta_id, SMTP_STID_SIZE);

				qm_ibdb_ta_clean(ibdb_ta);

				/* might happen if some logfiles are removed */
				if (ibdb_lta == NULL)
					break;
				bht_rm(bht_ta, ibdb_ta->ibt_ta_id,
					SMTP_STID_SIZE, ibdb_ta_free, NULL);
			}
			break;

		  case RT_IBDB_RCPT:
			++ri_ctx->rix_nrcpts;
			if (status == IBDB_RCPT_NEW)
			{
				ret = sm_id2idc(ibdb_rcpt->ibr_ta_id, &idc);
				if (ret == SM_SUCCESS &&
				    ri_ctx->rix_last_id < idc)
					ri_ctx->rix_last_id = idc;
				rcpt_id = (char *) sm_malloc(SMTP_RCPTID_SIZE
								+ 1);
				if (rcpt_id == NULL)
				{
					ret = sm_error_temp(SM_EM_Q_RDIBDB,
								ENOMEM);
					goto end;
				}
				sm_snprintf(rcpt_id, SMTP_RCPTID_SIZE,
					SMTP_RCPTID_FORMAT,
					ibdb_rcpt->ibr_ta_id,
					ibdb_rcpt->ibr_idx);
				ret = bht_add(bht_rcpt,
					rcpt_id, SMTP_RCPTID_SIZE,
					ibdb_rcpt, &bhte);
				if (sm_is_err(ret))
				{
					/* XXX what to do now? */
					QM_LEV_DPRINTFC(QDC_IBDBR, 0, (QM_DEBFP, "sev=ERROR, func=qm_ribdb, bht_add=%r, rcpt_id=%s\n", ret, rcpt_id));
					goto end;
				}
				ret = qm_ibdb_rcpt_new(&ibdb_rcpt);
				if (sm_is_err(ret) || ibdb_rcpt == NULL)
					goto end;
			}
			else
			{
				rcpt_id_T lrcpt_id;

				sm_snprintf(lrcpt_id, SMTP_RCPTID_SIZE,
					SMTP_RCPTID_FORMAT,
					ibdb_rcpt->ibr_ta_id,
					ibdb_rcpt->ibr_idx);
				ibdb_lrcpt = bht_find(bht_rcpt,
					lrcpt_id, SMTP_RCPTID_SIZE);

				/* might happen if some logfiles are removed */
				if (ibdb_lrcpt == NULL)
					break;

				ibdb_lta = bht_find(bht_ta,
						ibdb_rcpt->ibr_ta_id,
						SMTP_STID_SIZE);
				if (ibdb_lta != NULL)
				{
					if (status == IBDB_RCPT_TEMP)
						ibdb_lta->ibt_rcpts_temp++;
					else if (status == IBDB_RCPT_PERM)
						ibdb_lta->ibt_rcpts_perm++;
				}

				bht_rm(bht_rcpt, lrcpt_id, SMTP_RCPTID_SIZE,
					ibdb_rcpt_free, NULL);
			}
			break;

		  case RT_IBDB_HDRMOD:
			ibdb_lta = bht_find(bht_ta, ibdb_hdrmod->ibh_ta_id,
					SMTP_STID_SIZE);
			if (ibdb_lta == NULL)
			{
				/* clean up ibdb_hdrmod? */
				SM_CSTR_FREE(ibdb_hdrmod->ibh_hdr);
				break;
			}
			ret = sm_hdrmod_new(&ibdb_lta->ibt_hdrmodhd, true,
					&sm_hdrmod);
			if (sm_is_err(ret))
			{
				ret = sm_error_temp(SM_EM_Q_RDIBDB, ENOMEM);
				goto end;
			}
			sm_hdrmod->sm_hm_type = ibdb_hdrmod->ibh_type;
			sm_hdrmod->sm_hm_pos = ibdb_hdrmod->ibh_pos;
			sm_hdrmod->sm_hm_hdr = ibdb_hdrmod->ibh_hdr;
			ibdb_hdrmod->ibh_hdr = NULL;
			break;

		  default:
			if (sm_is_err(ret) && sm_error_value(ret) == ENOENT)
				ret = SM_SUCCESS;
			else if (SM_IO_EOF == ret || sm_error_perm(SM_EM_IO, EIO) == ret) {
				/*
				**  This may happen if an entry wasn't written
				**  completely. Just ignore that entry.
				*/

				sm_log_write(ri_ctx->rix_qmgr_ctx->qmgr_lctx,
					LIBQM_LCAT_LIB, LIBQM_LMOD_RDIBDB,
					SM_LOG_WARN, 2,
					"sev=WARN, func=qm_ribdb, ibdbr_get=%m"
					, ret);
				ret = SM_SUCCESS;
			}
			else
				QM_LEV_DPRINTFC(QDC_IBDBR, 0, (QM_DEBFP, "sev=ERROR, func=qm_ribdb, ibdbr_get=%r\n", ret));
			more = false;
			break;
		}
	} while (more);

	/* check recipients first to count them for TAs */
	bht_walk(bht_rcpt, qm_ibdb_rcpt_action, ri_ctx);

	/* Now run through open transactions */
	bht_walk(bht_ta, qm_ibdb_ta_action, ri_ctx);

  end:
	qm_ibdb_ta_free(ibdb_ta);
	ibdb_ta = NULL;
	qm_ibdb_rcpt_free(ibdb_rcpt);
	ibdb_rcpt = NULL;
	ibdbr_hdrmod_free(ibdb_hdrmod);

	return ret;
}

/*
**  QM_RDIBDB -- read INCEDB records and discard "closed" transactions.
**
**	Parameters:
**		qmgr_ctx -- QMGR context
**
**	Returns:
**		usual sm_error code
**
**	Notes:
**		This should be done before qm_edb_scan() such that the
**		new entries will be in EDBC.  If it is done afterwards,
**		EDBC must be populated too.
**
**		If this function runs as a thread concurrently with
**		the rest of QMGR then it also must provide proper locking
**		for EDBC. Running this as separate thread at startup
**		allows for a faster startup time because QMGR becomes
**		faster operational, but it requires that the IBDB
**		files are named properly to avoid overwriting etc.
**
**		The current algorithm suggests splitting this into
**		two functions or at least pass a parameter such that
**		reading an existing recovery directory happens only
**		on the first call (which need to be done before IBDB
**		is opened unless some fancy, complicated renaming is used).
**		Then IBDB is renamed to IBDB_R and that directory can
**		be read while QMGR is running.
**		Question: do we need to preserve the state (bhtables)
**		between calls in that case?
**
**	The main algorithm of this function is required in other
**	places too, e.g., the cleanup process.  Hence this should be
**	broken apart and put into a library...
*/

sm_ret_T
qm_rdibdb(qmgr_ctx_P qmgr_ctx)
{
	uint32_t first, last;
	sm_ret_T ret, ibdb_which, res;
	bool once;
	ibdbr_ctx_P ibdbrc;
	ri_ctx_P ri_ctx;

	SM_IS_QMGR_CTX(qmgr_ctx);

	/* Initialize most variables */
	ibdbrc = NULL;
	ibdb_which = 0;
	once = false;
	ret = qm_ri_ctx_new(qmgr_ctx, &ri_ctx);
	if (sm_is_err(ret))
	{
		QM_LEV_DPRINTFC(QDC_IBDBR, 0, (QM_DEBFP, "sev=ERROR, func=qm_rdibdb, qm_ri_ctx_new=%r\n", ret));
		goto end;
	}

  again:
	/*
	**  Prepare for recovery: determine whether IBDB or recovery IBDB
	**  exists. If the latter exists: just return SM_IBDB_RCVR_EXISTS;
	**  otherwise rename IBDB to recovery IBDB.
	*/

	ibdb_which = ibdbrcvr_open();
	QM_LEV_DPRINTFC(QDC_IBDBR, 4, (QM_DEBFP, "sev=INFO, func=qm_rdibdb, status=running, once=%d, ibdbrcvr_open=%r\n", once, ibdb_which));
	if (sm_is_err(ibdb_which))
	{
		ret = ibdb_which;
		goto end;
	}

	/* if neither directory exists: stop now */
	if (ibdb_which == SM_IBDB_NO_RCVR)
		goto end;

	/* open IBDB recovery directory */
	ret = ibdbf_get_seq((const char *)qmgr_ctx->qmgr_cnf.q_cnf_cdb_base,
			IBDB_NAME, IBDB_OFL_RCVR, &first, &last);
	QM_LEV_DPRINTFC(QDC_IBDBR, 4, (QM_DEBFP, "sev=DBG, func=qm_rdibdb, ibdbf_get_seq=%#x, first=%u, last=%u\n", ret, first, last));

	/* distinguish error values?? e.g., no directory? */
	if (sm_is_err(ret))
	{
		ret = SM_SUCCESS;
		goto end;	/* XXX no data? */
	}

	ret = ibdbr_open((const char *)qmgr_ctx->qmgr_cnf.q_cnf_cdb_base,
			IBDB_NAME, SM_IO_RDONLY, first, 0 /* unused */,
			IBDB_OFL_RCVR, &ibdbrc);
	if (sm_is_err(ret))
		goto end;
	ri_ctx->rix_ibdbrc = ibdbrc;

	/* evthr_time() isn't working yet; the system isn't running. */
	ri_ctx->rix_time = time(NULLT);
#if !SM_TEST_PRT
	ret = edb_req_new(qmgr_ctx->qmgr_edb, EDB_RQF_NONE,
			&ri_ctx->rix_edb_req, true);
	if (sm_is_err(ret))
		goto end;
	QM_LEV_DPRINTFC(QDC_IBDBR, 5, (QM_DEBFP, "rix_time=%ld\n", (long) ri_ctx->rix_time));
#endif

	/* Actually read IBDB */
	ret = qm_ribdb(ri_ctx);
	if (sm_is_err(ret))
		goto end;
	if (qmgr_ctx->qmgr_idc < ri_ctx->rix_last_id)
		qmgr_ctx->qmgr_idc = ri_ctx->rix_last_id;

#if !SM_TEST_PRT
	/* write all TAs and RCPTs to DEFEDB */
	ret = edb_wr_status(qmgr_ctx->qmgr_edb, &ri_ctx->rix_edb_req_hd);
	if (sm_is_err(ret))
	{
		QM_LEV_DPRINTFC(QDC_IBDBR, 0, (QM_DEBFP, "sev=ERROR, func=qm_rdibdb, edb_wr_status=%r\n", ret));
		goto end;
	}
#endif

	if (first <= last && ret == SM_SUCCESS)
		(void) ibdbr_unlink(ibdbrc, first, last);

  end:
	if (ibdbrc != NULL)
	{
		res = ibdbr_close(ibdbrc);

		/* Complain on error?? */
		if (sm_is_err(res))
			QM_LEV_DPRINTFC(QDC_IBDBR, 0, (QM_DEBFP, "sev=ERROR, func=qm_rdibdb, ibdbr_close=%r\n", res));
		ibdbrc = NULL;
	}

	/* check result?? */
	res = ibdbrcvr_close();
	QM_LEV_DPRINTFC(QDC_IBDBR, 2, (QM_DEBFP, "sev=DBG, func=qm_rdibdb, ibdbrcvr_close=%r\n", res));

	/*
	**  If the recovery directory exists then it was read first, hence
	**  in the next step the "normal" directory must be read.
	*/

	if (ibdb_which == SM_IBDB_RCVR_EXISTS)
	{
		if (once)
		{
			/* oops ... been there, done that? */
			QM_LEV_DPRINTFC(QDC_IBDBR, 0, (QM_DEBFP, "ERROR: endless loop in qm_rdibdb\n"));
			if (sm_is_success(ret))
				ret = sm_error_perm(SM_EM_IBDB,
						SM_E_UNEXPECTED);
		}
		else
		{
			once = true;
			goto again;
		}
	}

#if SM_TEST_PRT
	sm_io_fprintf(smioout, "#TAs:   %u\n", ri_ctx->rix_ntas);
	sm_io_fprintf(smioout, "#RCPTs: %u\n", ri_ctx->rix_nrcpts);
	sm_io_flush(smioout);
#endif
	qm_ri_ctx_free(ri_ctx);
	return ret;
}


syntax highlighted by Code2HTML, v. 0.9.1