/*
* 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