/*
 * Copyright (c) 2003-2006 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: edbc.c,v 1.30 2006/12/29 01:28:40 ca Exp $")
#include "sm/error.h"
#include "sm/memops.h"
#include "sm/heap.h"
#include "sm/assert.h"
#include "sm/str.h"
#include "sm/qmgrdbg.h"
#include "sm/edbc.h"
#include "edbc.h"

/*
**  EDBC_CMP -- compare two tnodes in envelope database cache, i.e.,
**	compare ecn_next_try of first element in list pointed to by tnode.
**
**	Parameters:
**		na -- edbc tnode a
**		nb -- edbc tnode b
**
**	Returns:
**		usual comparison code (-1, 0, 1)
**
**	Last code review: 2005-03-17 23:46:49
**	Last code change:
*/

static int
edbc_cmp(edbc_tnode_P na, edbc_tnode_P nb)
{
	time_T a;
	time_T b;

	SM_REQUIRE(na != NULL);
	SM_REQUIRE(nb != NULL);
	a = ECNL_FIRST(&na->ectn_hd)->ecn_next_try;
	b = ECNL_FIRST(&nb->ectn_hd)->ecn_next_try;
	if (a < b)
		return -1;
	else if (a == b)
		return 0;
	else
		return 1;
}

/* generate red/black tree functions */
RB_GENERATE(edbc_tree_S, edbc_tnode_S, ectn_entry, edbc_cmp)

/*
**  EDBC_OPEN -- open envelope database cache
**
**	Parameters:
**		edbc_ctx -- edbc_ctx
**		size -- estimated maximum size (unused)
**		max_size -- maximum size (0: unlimited)
**
**	Returns:
**		usual sm_error code; ENOMEM,
**
**	Last code review: 2005-03-18 00:32:44
**	Last code change:
*/

sm_ret_T
edbc_open(edbc_ctx_P *pedbc_ctx, uint size, uint max_size)
{
	int r;
	sm_ret_T ret;
	edbc_ctx_P edbc_ctx;

	SM_REQUIRE(pedbc_ctx != NULL);
	*pedbc_ctx = NULL;
	edbc_ctx = (edbc_ctx_P) sm_zalloc(sizeof(*edbc_ctx));
	if (NULL == edbc_ctx)
		return sm_error_temp(SM_EM_Q_EDBC, ENOMEM);
	r = pthread_mutex_init(&edbc_ctx->edbc_mutex, SM_PTHREAD_MUTEXATTR);
	if (r != 0) {
		ret = sm_error_perm(SM_EM_Q_EDBC, r);
		goto error;
	}

	RB_INIT(&edbc_ctx->edbc_root);
	edbc_ctx->edbc_max_entries = max_size;
	edbc_ctx->sm_magic = SM_EDBC_MAGIC;
	*pedbc_ctx = edbc_ctx;
	return SM_SUCCESS;

  error:
	if (edbc_ctx != NULL)
		sm_free_size(edbc_ctx, sizeof(*edbc_ctx));
	return ret;
}

/*
**  EDBC_CLOSE -- close envelope database cache
**
**	Parameters:
**		edbc_ctx -- edbc context
**
**	Returns:
**		SM_SUCCESS
**
**	Last code review: 2005-03-18 00:33:05
**	Last code change:
*/

sm_ret_T
edbc_close(edbc_ctx_P edbc_ctx)
{
	edbc_tnode_P edbc_tnode, edbc_tnode_nxt;
	edbc_node_P edbc_node, edbc_node_nxt;

	if (NULL == edbc_ctx)
		return SM_SUCCESS;
	SM_IS_EDBC(edbc_ctx);

	/* Lock EDBC before freeing it?? */

	/* Free the entire tree and the lists in it. */
	for (edbc_tnode = RB_MIN(edbc_tree_S, &edbc_ctx->edbc_root);
	     edbc_tnode != NULL;
	     edbc_tnode = edbc_tnode_nxt)
	{
		edbc_tnode_nxt = RB_NEXT(edbc_tree_S, &edbc_ctx->edbc_root, edbc_tnode);
		for (edbc_node = ECNL_FIRST(&edbc_tnode->ectn_hd);
		     edbc_node != ECNL_END(&edbc_tnode->ectn_hd);
		     edbc_node = edbc_node_nxt)
		{
			edbc_node_nxt = ECNL_NEXT(edbc_node);
			ECNL_REMOVE(&edbc_tnode->ectn_hd, edbc_node);
			sm_free_size(edbc_node, sizeof(*edbc_node));
		}

		(void) RB_REMOVE(edbc_tree_S, &edbc_ctx->edbc_root, edbc_tnode);
		sm_free_size(edbc_tnode, sizeof(*edbc_tnode));
	}

	(void) pthread_mutex_destroy(&edbc_ctx->edbc_mutex);
	edbc_ctx->sm_magic = SM_MAGIC_NULL;
	sm_free_size(edbc_ctx, sizeof(*edbc_ctx));
	return SM_SUCCESS;
}

/*
**  Note: the functions below don't perform locking,
**	it must be done by the caller!
**	Reason: usually the usage is:
**	lock edbc
**	perform several operations
**	unlock edbc
*/

/*
**  EDBC_ADD -- add an entry to envelope database cache
**
**	Parameters:
**		edbc_ctx -- edbc context
**		rcpt_id -- Recipient Id
**		next_try -- Time for next try
**		check -- check first whether entry exists
**
**	Returns:
**		usual sm_error code; ENOMEM, SM_E_FULL
**
**	Side Effects: none on error
**
**	Locking: must be performed by caller.
**
**	Last code review: 2005-03-18 00:36:23
**	Last code change:
*/

sm_ret_T
edbc_add(edbc_ctx_P edbc_ctx, rcpt_id_T rcpt_id, time_T next_try, bool check)
{
	sm_ret_T ret;
	edbc_tnode_P edbc_tnode, edbc_tnode_ret;
	edbc_node_P edbc_node;

	SM_IS_EDBC(edbc_ctx);
	if (check && edbc_exists(edbc_ctx, rcpt_id, NULL))
		return SM_SUCCESS;	/* other code? */

	if (edbc_ctx->edbc_max_entries > 0 &&
	    edbc_ctx->edbc_entries >= edbc_ctx->edbc_max_entries)
		return sm_error_temp(SM_EM_Q_EDBC, SM_E_FULL);
	edbc_tnode = NULL;
	edbc_node = (edbc_node_P) sm_zalloc(sizeof(*edbc_node));
	if (NULL == edbc_node)
		return sm_error_temp(SM_EM_Q_EDBC, ENOMEM);
	edbc_tnode = (edbc_tnode_P) sm_zalloc(sizeof(*edbc_tnode));
	if (NULL == edbc_tnode) {
		sm_free_size(edbc_node, sizeof(*edbc_node));
		return sm_error_temp(SM_EM_Q_EDBC, ENOMEM);
	}
	RCPT_ID_COPY(edbc_node->ecn_rcpt_id, rcpt_id);
	edbc_node->ecn_next_try = next_try;
	ECNL_INIT(&edbc_tnode->ectn_hd);
	ECNL_APP(&edbc_tnode->ectn_hd, edbc_node);
	edbc_tnode_ret = RB_FIND(edbc_tree_S, &edbc_ctx->edbc_root, edbc_tnode);
	if (NULL == edbc_tnode_ret) {
		edbc_tnode_ret = RB_INSERT(edbc_tree_S, &edbc_ctx->edbc_root,
					edbc_tnode);
		if (edbc_tnode_ret != NULL) {
			/* Internal error? Should not happen! */
			ret = sm_error_perm(SM_EM_Q_EDBC, EEXIST);
			goto error;
		}
	}
	else {
		/* Check first whether it's there? */
		ECNL_APP(&edbc_tnode_ret->ectn_hd, edbc_node);
		sm_free_size(edbc_tnode, sizeof(*edbc_tnode));
	}
	++edbc_ctx->edbc_entries;
	return SM_SUCCESS;

  error:
	SM_FREE_SIZE(edbc_tnode, sizeof(*edbc_tnode));
	SM_FREE_SIZE(edbc_node, sizeof(*edbc_node));
	return ret;
}

/*
**  EDBC_RM -- remove an entry from envelope database cache
**
**	Parameters:
**		edbc_ctx -- edbc context
**		edbc_node -- edbc node (to be free()d)
**
**	Returns:
**		usual sm_error code; SM_E_NOTFOUND, SM_E_UNEXPECTED
**
**	Side Effects: none on error (except for unexpected error)
**
**	Locking: must be performed by caller.
**
**	Last code review: 2005-03-24 23:03:08
**	Last code change: 2005-03-18 00:49:35
*/

sm_ret_T
edbc_rm(edbc_ctx_P edbc_ctx, edbc_node_P edbc_node)
{
	edbc_tnode_T edbc_tnode;
	edbc_node_T edbc_node_search;
	edbc_tnode_P edbc_tnode_ret;

	SM_IS_EDBC(edbc_ctx);
	SM_REQUIRE(edbc_node != NULL);

	sm_memzero(&edbc_node_search, sizeof(edbc_node_search));
	sm_memzero(&edbc_tnode, sizeof(edbc_tnode));
	ECNL_INIT(&(edbc_tnode.ectn_hd));
	ECNL_APP(&(edbc_tnode.ectn_hd), &edbc_node_search);
	edbc_node_search.ecn_next_try = edbc_node->ecn_next_try;
	edbc_tnode_ret = RB_FIND(edbc_tree_S, &edbc_ctx->edbc_root, &edbc_tnode);
	if (NULL == edbc_tnode_ret)
		return sm_error_temp(SM_EM_Q_EDBC, SM_E_NOTFOUND);
	ECNL_REMOVE(&edbc_tnode_ret->ectn_hd, edbc_node);
	SM_FREE_SIZE(edbc_node, sizeof(*edbc_node));
	if (ECNL_EMPTY(&edbc_tnode_ret->ectn_hd)) {
		if (RB_REMOVE(edbc_tree_S, &edbc_ctx->edbc_root, edbc_tnode_ret) == NULL)
		{
			/* internal error: the node is suddenly gone? */
			return sm_error_temp(SM_EM_Q_EDBC, SM_E_UNEXPECTED);
		}
		SM_FREE_SIZE(edbc_tnode_ret, sizeof(*edbc_tnode_ret));
	}
	SM_ASSERT(edbc_ctx->edbc_entries > 0);
	--edbc_ctx->edbc_entries;
	return SM_SUCCESS;
}

/*
**  EDBC_FIRST -- return first entry from envelope database cache
**
**	Parameters:
**		edbc_ctx -- edbc context
**
**	Returns:
**		edbc_node
**
**	Locking: must be performed by caller.
**
**	Last code review: 2005-04-01 17:59:40
**	Last code change:
*/

edbc_node_P
edbc_first(edbc_ctx_P edbc_ctx)
{
	edbc_tnode_P edbc_tnode;

	SM_IS_EDBC(edbc_ctx);
	edbc_tnode = RB_MIN(edbc_tree_S, &edbc_ctx->edbc_root);
	if (NULL == edbc_tnode)
		return NULL;
	return ECNL_FIRST(&edbc_tnode->ectn_hd);
}

/*
**  EDBC_NEXT -- return next entry from envelope database cache
**
**	Parameters:
**		edbc_ctx -- edbc context
**		edbc_node -- current edbc node
**
**	Returns:
**		edbc_node (NULL if non exists)
**		note: NULL is also returned on error... change API??
**
**	Locking: must be performed by caller.
**
**	Note: this is an expensive function because it needs to find
**		the tree node for edbc_node on every call.
**		It would be more efficient to pass edbc_tnode
**		as "cursor" to this function. Alternatively a "link"
**		type could be chose that allows access to the head
**		from each element (pointer back to head; speed vs memory).
**
**	Last code review: 2005-03-18 01:00:34; see comments (above)
**	Last code change: 2005-03-18 00:09:15
*/

edbc_node_P
edbc_next(edbc_ctx_P edbc_ctx, edbc_node_P edbc_node)
{
	edbc_tnode_T edbc_tnode;
	edbc_node_T edbc_node_search;
	edbc_tnode_P edbc_tnode_ret;
	edbc_node_P edbc_node_nxt;

	SM_IS_EDBC(edbc_ctx);
	SM_REQUIRE(edbc_node != NULL);
	edbc_node_nxt = ECNL_NEXT(edbc_node);

	/* NULL == ECNL_END(&edbc_tnode_ret->ectn_hd) */
	/* SM_ASSERT(NULL == ECNL_END(...)); */
	if (edbc_node_nxt != NULL)
		return edbc_node_nxt;

	sm_memzero(&edbc_tnode, sizeof(edbc_tnode));
	sm_memzero(&edbc_node_search, sizeof(edbc_node_search));
	ECNL_INIT(&(edbc_tnode.ectn_hd));
	ECNL_APP(&(edbc_tnode.ectn_hd), &edbc_node_search);
	edbc_node_search.ecn_next_try = edbc_node->ecn_next_try;
	edbc_tnode_ret = RB_FIND(edbc_tree_S, &edbc_ctx->edbc_root, &edbc_tnode);
	if (NULL == edbc_tnode_ret) {
		QM_LEV_DPRINTF(0, (QM_DEBFP,
			"sev=ERROR, func=edbc_next, edbc_tnode_ret=NULL, edbc_node=%p, next_try=%6ld, id=%s\n"
			, edbc_node, (long) edbc_node->ecn_next_try
			, edbc_node->ecn_rcpt_id));
		/* sm_error_perm(SM_EM_Q_EDBC, SM_E_NOTFOUND); internal error */
		return NULL;
	}
	if (edbc_node_nxt != ECNL_END(&edbc_tnode_ret->ectn_hd))
		return edbc_node_nxt;
	edbc_tnode_ret = RB_NEXT(edbc_tree_S, &edbc_ctx->edbc_root, edbc_tnode_ret);
	if (NULL == edbc_tnode_ret)
		return NULL;
		/* sm_error_perm(SM_EM_Q_EDBC, SM_E_NOMORE); */
	return ECNL_FIRST(&edbc_tnode_ret->ectn_hd);
}

#if QMGR_DEBUG
/*
**  EDBC_PRINT -- print content of EDBC
**
**	Parameters:
**		edbc_ctx -- edbc context
**
**	Returns:
**		none
**
**	Locking: must be performed by caller.
*/

void
ebdc_print(edbc_ctx_P edbc_ctx)
{
	edbc_node_P edbc_node;

	edbc_node = edbc_first(edbc_ctx);
	while (edbc_node != NULL) {
		QM_LEV_DPRINTF(4, (QM_DEBFP,
			"sev=DBG, func=edbc_next, edbc_node=%p, time=%6ld, rcpt_id=%s\n"
			, edbc_node, (long) edbc_node->ecn_next_try
			, edbc_node->ecn_rcpt_id));
		edbc_node = edbc_next(edbc_ctx, edbc_node);
	}
}
#endif /* QMGR_DEBUG */


syntax highlighted by Code2HTML, v. 0.9.1