/* * 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: occh.c,v 1.34 2007/01/20 15:48:33 ca Exp $") #include "sm/types.h" #include "sm/assert.h" #include "sm/magic.h" #include "sm/error.h" #include "sm/heap.h" #include "sm/memops.h" #include "sm/time.h" #include "sm/mta.h" #include "sm/qmgrdbg.h" #include "occ.h" #include "sm/io.h" #define LOCK_DPRINTF(x) /* ** Notes: ** Locking is currently done via dadb_ctx (2004-02-10) ** ** It might be useful to add a "free" function that actually ** frees entries instead of returning them to the freelist. ** Maybe this should just be a parameter to occ_entry_free()? ** Theoretically it would also be possible to count the number ** of entries in the "free-list" and free entries when a certain ** threshold is exceeded. However, this isn't as useful as it may seem ** because the number of outgoing connections is restricted ** (by the number of threads in the DAs), hence the number of entries ** in the "free-list" doesn't grow without bounds. */ /* ** OCC_ENTRY_FREE -- free OCC entry (return it to free-list) ** ** Parameters: ** occ_fl_hd -- head of OCC freelist ** occ_entry -- OCC entry ** occ_mutex -- mutex to use for locking ** locktype -- kind of locking ** ** Returns: ** SM_SUCCESS except for (un)lock errors ** ** Side Effects: none on error (except if unlock fails) ** ** Locking: locks occ_mutex if requested ** ** Last code review: 2005-03-17 04:52:37 ** Last code change: */ sm_ret_T occ_entry_free(occ_entry_hd_P occ_fl_hd, occ_entry_P occ_entry, pthread_mutex_t *occ_mutex, thr_lock_T locktype) { #undef SMFCT #define SMFCT "occ_entry_free" int r; if (NULL == occ_entry) return SM_SUCCESS; if (thr_lock_it(locktype)) { r = smthread_mutex_lock(occ_mutex); SM_LOCK_OK(r); if (r != 0) return sm_error_perm(SM_EM_OCC, r); } SM_IS_OCCE(occ_entry); occ_entry->occe_srv_ipv4 = 0; occ_entry->occe_flags = 0; occ_entry->occe_init_conc = 0; occ_entry->occe_cur_conc = 0; occ_entry->occe_max_conc = 0; occ_entry->occe_open_se = 0; occ_entry->occe_open_ta = 0; #if 0 occ_entry->occe_last_conn = 0; #endif occ_entry->occe_last_upd = 0; occ_entry->occe_timeout = OCCE_TO; OCCFL_PRE(occ_fl_hd, occ_entry); /* do not reset occ_entry->sm_magic, the entry can be reused */ if (thr_unl_always(locktype)) { r = smthread_mutex_unlock(occ_mutex); SM_ASSERT(r == 0); if (r != 0) return sm_error_perm(SM_EM_OCC, r); } return SM_SUCCESS; } #if 0 ///* //** OCC_ENTRY_GET -- get/create new OCC entry //** //** Parameters: //** dadb_ctx -- DADB context //** pocc_entry -- pointer to OCC entry (output) //** locktype -- kind of locking //** //** Returns: //** usual sm_error code //** //** Locking: locks dadb_ctx if requested //*/ // //sm_ret_T //occ_entry_get(dadb_ctx_P dadb_ctx, occ_entry_P *pocc_entry, thr_lock_T locktype) //{ // sm_ret_T ret; // int r; // occ_entry_P occ_entry; // // SM_IS_DADB(dadb_ctx); // if (thr_lock_it(locktype)) // { // r = smthread_mutex_lock(occ_mutex); // SM_LOCK_OK(r); // if (r != 0) // return sm_error_perm(SM_EM_OCC, r); // } // // if (OCCFL_EMPTY(occ_fl_hd)) // { // occ_entry = (occ_entry_P) sm_zalloc(sizeof(*occ_entry)); // if (NULL == occ_entry) // goto enomem; //#if OCC_CHECK // occ_entry->sm_magic = SM_OCCE_MAGIC; //#endif // } // else // { // occ_entry = OCCFL_FIRST(occ_fl_hd); // SM_IS_OCCE(occ_entry); // OCCFL_REMOVE(occ_fl_hd); // } // // *pocc_entry = occ_entry; // if (thr_unl_no_err(locktype)) // { // r = smthread_mutex_unlock(occ_mutex); // if (r != 0) // return sm_error_perm(SM_EM_OCC, r); // } // return SM_SUCCESS; // // enomem: // ret = sm_error_temp(SM_EM_OCC, ENOMEM); // // /* clean up...? nothing to do right now */ // if (thr_unl_if_err(locktype)) // (void) smthread_mutex_unlock(occ_mutex); // return ret; //} #endif /* 0 */ /* ** OCC_ENTRY_NEW -- get/create new OCC entry ** ** Parameters: ** occ_ht -- OCC hash table ** occ_fl_hd -- head of OCC freelist ** ipv4 -- IPv4 address of server (HACK) ** pocc_entry -- pointer to OCC entry (output) ** occ_mutex -- mutex to use for locking ** locktype -- kind of locking ** ** Returns: ** usual sm_error code; ENOMEM, ** ** Side Effects: none on error (except if unlock fails) ** ** Locking: locks occ_mutex if requested ** ** Last code review: 2005-03-17 05:12:52 ** Last code change: */ sm_ret_T occ_entry_new(OCC_HT_P occ_ht, occ_entry_hd_P occ_fl_hd, ipv4_T ipv4, occ_entry_P *pocc_entry, pthread_mutex_t *occ_mutex, thr_lock_T locktype) { #undef SMFCT #define SMFCT "occ_entry_new" sm_ret_T ret; int r; bool allocated; occ_entry_P occ_entry; #if !DA_OCC_RSC bht_entry_P bht_entry; #endif SM_REQUIRE(pocc_entry != NULL); allocated = false; if (thr_lock_it(locktype)) { r = smthread_mutex_lock(occ_mutex); SM_LOCK_OK(r); if (r != 0) return sm_error_perm(SM_EM_OCC, r); } QM_LEV_DPRINTF(4, (QM_DEBFP, "func=occ_entry_new, empty=%d\n", OCCFL_EMPTY(occ_fl_hd))); if (OCCFL_EMPTY(occ_fl_hd)) { occ_entry = (occ_entry_P) sm_zalloc(sizeof(*occ_entry)); if (NULL == occ_entry) { ret = sm_error_temp(SM_EM_OCC, ENOMEM); goto error; } #if OCC_CHECK occ_entry->sm_magic = SM_OCCE_MAGIC; #endif allocated = true; } else { occ_entry = OCCFL_FIRST(occ_fl_hd); SM_IS_OCCE(occ_entry); OCCFL_REMOVE(occ_fl_hd); } occ_entry->occe_srv_ipv4 = ipv4; /* HACK! */ occ_entry->occe_init_conc = 0; occ_entry->occe_cur_conc = 0; occ_entry->occe_max_conc = 0; occ_entry->occe_timeout = OCCE_TO; QM_LEV_DPRINTF(4, (QM_DEBFP, "func=occ_entry_new, occ_entry=%p, ipv4=%A\n", occ_entry, (ipv4_T) ipv4)); #if DA_OCC_RSC ret = rsc_add(occ_ht, true, (char *)&occ_entry->occe_srv_ipv4, sizeof(occ_entry->occe_srv_ipv4), occ_entry, NULL, THR_NO_LOCK); #else ret = bht_add(occ_ht, (char *)&occ_entry->occe_srv_ipv4, sizeof(occ_entry->occe_srv_ipv4), occ_entry, &bht_entry); #endif if (sm_is_err(ret)) { QM_LEV_DPRINTF(4, (QM_DEBFP, "sev=ERROR, func=occ_entry_new, bht_add=%x", ret)); goto error; } *pocc_entry = occ_entry; if (thr_unl_no_err(locktype)) { r = smthread_mutex_unlock(occ_mutex); if (r != 0 && sm_is_success(ret)) ret = sm_error_perm(SM_EM_OCC, r); } return ret; error: /* clean up...? nothing to do right now */ if (thr_unl_if_err(locktype)) { r = smthread_mutex_unlock(occ_mutex); SM_ASSERT(r == 0); } if (allocated && occ_entry != NULL) sm_free_size(occ_entry, sizeof(*occ_entry)); *pocc_entry = NULL; /* just a courtesy for the caller */ return ret; } /* ** OCC_ENTRY_FIND -- find OCC entry by IPv4 address ** ** Parameters: ** occ_ht -- OCC context ** ipv4 -- IPv4 address ** pocc_entry -- pointer to DA OCC entry (output) ** occ_mutex -- mutex to use for locking ** locktype -- kind of locking ** ** Returns: ** usual sm_error code; SM_E_NOTFOUND, (un)lock errors ** ** Side Effects: none on error (except if unlock fails) ** ok: may reset conc.conn. limit (if it is zero but timed out) ** ** Locking: locks occ_mutex if requested ** ** Last code review: 2005-03-17 05:14:35 ** Last code change: */ sm_ret_T occ_entry_find(OCC_HT_P occ_ht, ipv4_T ipv4, occ_entry_P *pocc_entry, pthread_mutex_t *occ_mutex, thr_lock_T locktype) { #undef SMFCT #define SMFCT "occ_entry_find" sm_ret_T ret; int r; occ_entry_P occ_entry; SM_REQUIRE(pocc_entry != NULL); *pocc_entry = NULL; /* just a courtesy for the caller */ if (thr_lock_it(locktype)) { r = smthread_mutex_lock(occ_mutex); SM_LOCK_OK(r); if (r != 0) return sm_error_perm(SM_EM_OCC, r); } ret = SM_SUCCESS; #if DA_OCC_RSC occ_entry = (occ_entry_P) rsc_lookup(occ_ht, (const char *)&ipv4, sizeof(ipv4), THR_NO_LOCK); #else occ_entry = (occ_entry_P) bht_find(occ_ht, (const char *)&ipv4, sizeof(ipv4)); #endif QM_LEV_DPRINTF(4, (QM_DEBFP, "func=occ_entry_find, entry=%p\n", occ_entry)); if (NULL == occ_entry) ret = sm_error_perm(SM_EM_OCC, SM_E_NOTFOUND); else { SM_IS_OCCE(occ_entry); /* ** If currently #if 0 ** less connections than initial connections #else ** no connections #endif ** are allowed and the entry timed out then reset it ** to the initial value. ** Should it be reset to a "reset" value (usually smaller ** than the initial value)? ** The initial value is "we don't know anything about this ** destination", the "reset" value would be: "it didn't work ** before, but let's try again". ** ** Should last_conn be used to "reset" an entry, i.e., ** clear the number of open sessions? ** If the number gets out of sync then how should it ** recover? It could run some sanity check by looking ** at all dadb entries... */ if ( #if 0 occ_entry->occe_cur_conc < occ_entry->occe_init_conc #else occ_entry->occe_cur_conc == 0 #endif && occ_entry->occe_last_upd + occ_entry->occe_timeout <= time(NULLT)) occ_entry->occe_cur_conc = occ_entry->occe_init_conc; *pocc_entry = occ_entry; } if ((!sm_is_err(ret) && thr_unl_no_err(locktype)) || (sm_is_err(ret) && thr_unl_if_err(locktype))) { r = smthread_mutex_unlock(occ_mutex); SM_ASSERT(r == 0); if (r != 0 && sm_is_success(ret)) ret = sm_error_perm(SM_EM_OCC, r); } return ret; }