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