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


syntax highlighted by Code2HTML, v. 0.9.1