/*
 * 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: ibdbc.c,v 1.17 2006/08/05 04:19:29 ca Exp $")
#include "sm/assert.h"
#include "sm/error.h"
#include "sm/memops.h"
#include "sm/time.h"
#include "sm/heap.h"
#include "sm/rpool.h"
#include "sm/bhtable.h"
#include "sm/ibdb.h"
#include "ibdbc.h"

/*
**  IBDB Cache:
**
**  This cache (organized as hash table: ibdb_ctx->ibdb_ct, index is
**  TA/RCPT id) keeps track in which IBDB sequence number a record (TA
**  or RCPT) is stored.
**
**  When an entry is added to IBDB, it is also added to the cache:
**  sm_ibc_ta_add() and sm_ibc_rcpt_add().
**
**  When an entry is closed in IBDB, then it is removed from the cache:
**  sm_ibc_ta_rm() and sm_ibc_rcpt_rm().
**
**  One walk through the cache can be used to determine the lowest
**  sequence number that is still referenced sm_ibc_low_seq(). Any IBDB
**  file that has a lower sequence number can be removed, see
**  libibdb/ibdb.c: ibdb_clean().
*/


/*
**  Problems:
**	what to do when the cache is full?
**	a graceful fallback to another cleanup algorithm would be the best,
**	but that's overkill for the first version.
**
**  Todo:
**	Is it possible to count references to an IBDB files?
**	that is, create an array of used files (first - seq)
**	and then increase a (reference) counter for each open
**	transaction in a file?
**	Probably not: a file without "references" may contain a "CLOSE"
**	transaction for an "OPEN" transaction in an earlier file.
**	Removing the latter will breaks things...
*/

/*
**  IBC_E_NEW -- allocate IBDB cache entry
**
**	Parameters:
**		pibc_e -- (pointer to) IBDB cache entry (output)
**
**	Returns:
**		usual sm_error code; ENOMEM
**
**	Side Effects: none on error
**
**	Last code review: 2005-03-30 23:18:58
**	Last code change:
*/

static sm_ret_T
ibc_e_new(ibc_e_P *pibc_e)
{
	ibc_e_P ibc_e;

	SM_REQUIRE(pibc_e != NULL);
	ibc_e = (ibc_e_P) sm_zalloc(sizeof(*ibc_e));
	if (NULL == ibc_e)
		return sm_error_temp(SM_EM_IBDB, ENOMEM);
	*pibc_e = ibc_e;
	return SM_SUCCESS;
}

/*
**  IBC_E_FREE -- free IBDB cache entry
**
**	Parameters:
**		ibc_e -- IBDB cache entry
**
**	Returns:
**		SM_SUCCESS
**
**	Last code review: 2005-03-30 23:46:02
**	Last code change:
*/

static sm_ret_T
ibc_e_free(ibc_e_P ibc_e)
{
	if (NULL == ibc_e)
		return SM_SUCCESS;
	sm_free_size(ibc_e, sizeof(*ibc_e));
	return SM_SUCCESS;
}

/*
**  BHT_IBC_E_FREE -- free IBDB cache entry (callback function)
**
**	Parameters:
**		value -- IBDB cache entry
**		key -- rcpt id (unused)
**		ctx -- context (ignored, for compatibility with bhfree_F)
**
**	Returns:
**		none
**
**	Last code review: 2005-03-30 23:45:46
**	Last code change:
*/

/* ARGSUSED1 */
static void
bht_ibc_e_free(void *value, void *key, void *ctx)
{
	ibc_e_P ibc_e;

	if (NULL == value)
		return;
	ibc_e = (ibc_e_P) value;
	sm_free_size(ibc_e, sizeof(*ibc_e));
}

/*
**  SM_IBC_TA_ADD -- Add a new TA to IBC
**
**	Parameters:
**		bht -- Hash table
**		ta_id -- Transaction ID
**		seq -- sequence number
**
**	Returns:
**		usual sm_error code; ENOMEM, SM_E_FULL
**
**	Locking:
**		must be done by caller (ibdb_ctx->ibdb_mutex)
**
**	Last code review: 2005-03-31 04:53:56
**	Last code change: 2005-03-31 04:53:31
*/

sm_ret_T
sm_ibc_ta_add(bht_P bht, sessta_id_P ta_id, uint32_t seq)
{
	sm_ret_T ret;
	ibc_e_P ibc_e;
	bht_entry_P bhte;

	ret = ibc_e_new(&ibc_e);
	if (sm_is_err(ret))
		return ret;
	SESSTA_COPY(ibc_e->ibc_id, ta_id);
	ibc_e->ibc_sequence = seq;
	ibc_e->ibc_type = IBC_TA;
	ret = bht_add(bht, ibc_e->ibc_id, SMTP_STID_SIZE, ibc_e, &bhte);
	return ret;
}

/*
**  SM_IBC_RCPT_ADD -- Add a new RCPT to IBC
**
**	Parameters:
**		bht -- Hash table
**		ta_id -- Transaction ID
**		rcpt_idx -- Transaction ID
**		seq -- sequence number
**
**	Returns:
**		usual sm_error code; ENOMEM, SM_E_FULL
**
**	Locking:
**		must be done by caller (ibdb_ctx->ibdb_mutex)
**
**	Last code review: 2005-03-30 23:33:31
**	Last code change: 2005-03-30 23:20:19
*/

sm_ret_T
sm_ibc_rcpt_add(bht_P bht, sessta_id_P ta_id, rcpt_idx_T rcpt_idx, uint32_t seq)
{
	sm_ret_T ret;
	ibc_e_P ibc_e;
	bht_entry_P bhte;

	ret = ibc_e_new(&ibc_e);
	if (sm_is_err(ret))
		return ret;
	sm_snprintf(ibc_e->ibc_id, SMTP_RCPTID_SIZE, SMTP_RCPTID_FORMAT,
		ta_id, rcpt_idx);
	ibc_e->ibc_sequence = seq;
	ibc_e->ibc_type = IBC_RCPT;
	ret = bht_add(bht, ibc_e->ibc_id, SMTP_RCPTID_SIZE, ibc_e, &bhte);
	if (sm_is_err(ret))
		ibc_e_free(ibc_e);
	return ret;
}

/*
**  SM_IBC_TA_RM -- Remove a TA from IBC
**
**	Parameters:
**		bht -- Hash table
**		ta_id -- Transaction ID
**
**	Returns:
**		usual sm_error code; SM_E_NOTFOUND
**
**	Locking:
**		must be done by caller (ibdb_ctx->ibdb_mutex)
**
**	Last code review: 2005-03-31 04:52:23
**	Last code change:
*/

sm_ret_T
sm_ibc_ta_rm(bht_P bht, sessta_id_P ta_id)
{
	ibc_e_P ibc_e;

	ibc_e = bht_find(bht, ta_id, SMTP_STID_SIZE);
	if (NULL == ibc_e)
		return sm_error_perm(SM_EM_IBDB, SM_E_NOTFOUND);
	bht_rm(bht, ta_id, SMTP_STID_SIZE, bht_ibc_e_free, NULL);
	return SM_SUCCESS;
}

/*
**  SM_IBC_RCPT_RM -- Remove a RCPT from IBC
**
**	Parameters:
**		bht -- Hash table
**		ta_id -- Transaction ID
**		rcpt_idx -- Transaction ID
**
**	Returns:
**		usual sm_error code; SM_E_NOTFOUND
**
**	Locking:
**		must be done by caller (ibdb_ctx->ibdb_mutex)
**
**	Last code review: 2005-03-30 23:46:42
**	Last code change: 2005-03-30 23:43:55
*/

sm_ret_T
sm_ibc_rcpt_rm(bht_P bht, sessta_id_P ta_id, rcpt_idx_T rcpt_idx)
{
	ibc_e_P ibc_e;
	rcpt_id_T rcpt_id;

	sm_snprintf(rcpt_id, SMTP_RCPTID_SIZE, SMTP_RCPTID_FORMAT,
			ta_id, rcpt_idx);
	ibc_e = bht_find(bht, rcpt_id, SMTP_RCPTID_SIZE);
	if (NULL == ibc_e)
		return sm_error_perm(SM_EM_IBDB, SM_E_NOTFOUND);
	bht_rm(bht, rcpt_id, SMTP_RCPTID_SIZE, bht_ibc_e_free, NULL);
	return SM_SUCCESS;
}

/*
**  SM_IBC_WALK -- Callback function: determine smallest sequence number in bht
**
**	Parameters:
**		bhte -- Hash table entry
**		ctx -- pointer to current lowest value
**
**	Returns:
**		SM_SUCCESS
**
**	Last code review: 2005-03-31 04:41:28
**	Last code change:
*/

static sm_ret_T
sm_ibc_walk(bht_entry_P bhte, void *ctx)
{
	ibc_e_P ibc_e;
	uint32_t *pseq;

	SM_REQUIRE(bhte != NULL);
	SM_REQUIRE(ctx != NULL);

	ibc_e = (ibc_e_P) bhte->bhe_value;
	pseq = (uint32_t *) ctx;
	if (ibc_e->ibc_sequence < *pseq)
		*pseq = ibc_e->ibc_sequence;
	return SM_SUCCESS;
}

/*
**  SM_IBC_LOW_SEQ -- determine smallest sequence number in bht
**
**	Parameters:
**		bht -- Hash table
**		pseq -- pointer to sequence number (output)
**
**	Returns:
**		SM_SUCCESS
**
**	Locking:
**		must be done by caller (ibdb_ctx->ibdb_mutex)
**
**	Last code review: 2005-03-31 04:41:44
**	Last code change:
*/

sm_ret_T
sm_ibc_low_seq(bht_P bht, uint32_t *pseq)
{
	sm_ret_T ret;

	SM_REQUIRE(bht != NULL);
	SM_REQUIRE(pseq != NULL);
	*pseq = UINT32_MAX;
	ret = bht_walk(bht, sm_ibc_walk, pseq);
	if (*pseq == UINT32_MAX)
		*pseq = 0;
	return ret;
}


syntax highlighted by Code2HTML, v. 0.9.1