/*
 * 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: ssocc.c,v 1.22 2007/10/25 01:42:30 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/bhtable.h"
#include "sm/ssocc.h"
#include "sm/qmgr-int.h"

/*
**  SSOCC_CLOSE -- close a SSOCC_CTX
**
**	Parameters:
**		ssocc_ctx -- SSOCC context
**
**	Returns:
**		usual sm_error code
**
**	Locking: no locking, destroys SSOCC (and hence lock)
*/

sm_ret_T
ssocc_close(ssocc_ctx_P ssocc_ctx)
{
	if (ssocc_ctx == NULL)
		return SM_SUCCESS;
	SM_IS_SSOCC(ssocc_ctx);
#if SSOCC_RSC
	rsc_free(ssocc_ctx->ssocc_ht);
#else
	bht_destroy(ssocc_ctx->ssocc_ht, NULL, NULL);
#endif

	sm_connctl_free(ssocc_ctx->ssocc_cctx);
	(void) pthread_mutex_destroy(&ssocc_ctx->ssocc_mutex);
	sm_free_size(ssocc_ctx, sizeof(*ssocc_ctx));
	return SM_SUCCESS;
}

/*
**  SSOCC_OPEN -- open a new SSOCC
**
**	Parameters:
**		pssocc_ctx -- pointer to SSOCC context (output)
**		size -- maximum size of SSOCC
**		connctl_size -- size of connection control table
**
**	Returns:
**		usual sm_error code
*/

sm_ret_T
ssocc_open(ssocc_ctx_P *pssocc_ctx, uint size, uint connctl_size)
{
	int r;
	sm_ret_T ret;
	ssocc_ctx_P ssocc_ctx;

	SM_REQUIRE(pssocc_ctx != NULL);
	ssocc_ctx = (ssocc_ctx_P) sm_zalloc(sizeof(*ssocc_ctx));
	if (ssocc_ctx == NULL)
	{
		ret = sm_error_temp(SM_EM_Q, ENOMEM);
		goto error;
	}

#if SSOCC_RSC
	ssocc_ctx->ssocc_ht = rsc_new(size * 2, size * 3,
		rsc_occe_create, rsc_occe_delete, (void *) ssocc_ctx);
#else
	ssocc_ctx->ssocc_ht = bht_new(size, size * 2);
#endif
	if (ssocc_ctx->ssocc_ht == NULL)
	{
		ret = sm_error_temp(SM_EM_Q, ENOMEM);
		goto errfree;
	}
	OCCFL_INIT(&ssocc_ctx->ssocc_fl_hd);

	/* CONF constants? configuration parameters? */
	ret = sm_connctl_new(connctl_size, 5, &ssocc_ctx->ssocc_cctx);
	if (sm_is_err(ret))
		goto errfree;

	r = pthread_mutex_init(&ssocc_ctx->ssocc_mutex, SM_PTHREAD_MUTEXATTR);
	if (r != 0)
	{
		ret = sm_error_perm(SM_EM_Q, r);
		goto errfree;
	}

#if SSOCC_CHECK
	ssocc_ctx->sm_magic = SM_SSOCC_MAGIC;
#endif
	*pssocc_ctx = ssocc_ctx;
	return SM_SUCCESS;

  errfree:
	if (ssocc_ctx != NULL)
	{
#if SSOCC_RSC
		rsc_free(ssocc_ctx->ssocc_ht);
#else
		bht_destroy(ssocc_ctx->ssocc_ht, NULL, NULL);
#endif
		sm_connctl_free(ssocc_ctx->ssocc_cctx);

		sm_free_size(ssocc_ctx, sizeof(*ssocc_ctx));
	}
  error:
	return ret;
}

/*
**  SSOCC_ENTRY_FREE -- free SSOCC entry (return it to free-list)
**
**	Parameters:
**		ssocc_ctx -- SSOCC context
**		ssocc_entry -- SSOCC entry
**		locktype -- kind of locking
**
**	Returns:
**		usual sm_error code
**
**	Locking: check unlocking! XXX
*/

sm_ret_T
ssocc_entry_free(ssocc_ctx_P ssocc_ctx, ssocc_entry_P ssocc_entry, thr_lock_T locktype)
{
	int r;

	SM_IS_SSOCC(ssocc_ctx);
	if (ssocc_entry == NULL)
		return SM_SUCCESS;
	if (thr_lock_it(locktype))
	{
		r = pthread_mutex_lock(&ssocc_ctx->ssocc_mutex);
		SM_LOCK_OK(r);
		if (r != 0)
			return sm_error_perm(SM_EM_Q, r);
	}

	SM_IS_SSOCCE(ssocc_entry);

#if SSOCC_RSC
	(void) rsc_rm(ssocc_ctx->ssocc_ht,
			(char *)&ssocc_entry->ssocce_srv_ipv4,
			sizeof(ssocc_entry->ssocce_srv_ipv4), THR_NO_LOCK);
#else /* SSOCC_RSC */
	(void) bht_rm(ssocc_ctx->ssocc_ht,
			(char *)&ssocc_entry->ssocce_srv_ipv4,
			sizeof(ssocc_entry->ssocce_srv_ipv4),
			NULL, NULL);
#endif /* SSOCC_RSC */

	ssocc_entry->ssocce_srv_ipv4 = 0;
	ssocc_entry->ssocce_open_se = 0;
	ssocc_entry->ssocce_open_se_exc = 0;
	ssocc_entry->ssocce_last_conn = 0;
	SSOCCFL_PRE(&ssocc_ctx->ssocc_fl_hd, ssocc_entry);
	/* XXX free entry if there are too many? */

	/* do not reset ssocc_entry->sm_magic, the entry can be reused */

	if (thr_unl_always(locktype))
	{
		r = pthread_mutex_unlock(&ssocc_ctx->ssocc_mutex);
		if (r != 0)
		{
QM_LEV_DPRINTF(1, (QM_DEBFP, "sev=ERROR, func=ssocc_entry_free, unlock=%d\n", r));
			/* XXX ??? COMPLAIN */
			return sm_error_perm(SM_EM_Q, r);
		}
	}
	return SM_SUCCESS;
}

#if 0
/*
**  SSOCC_ENTRY_GET -- get/create new SSOCC entry
**
**	Parameters:
**		ssocc_ctx -- SSOCC context
**		pssocc_entry -- pointer to SSOCC entry (output)
**		locktype -- kind of locking
**
**	Returns:
**		usual sm_error code
*/

sm_ret_T
ssocc_entry_get(ssocc_ctx_P ssocc_ctx, ssocc_entry_P *pssocc_entry, thr_lock_T locktype)
{
	sm_ret_T ret;
	int r;
	ssocc_entry_P ssocc_entry;

	SM_IS_SSOCC(ssocc_ctx);
	if (thr_lock_it(locktype))
	{
		r = pthread_mutex_lock(&ssocc_ctx->ssocc_mutex);
		SM_LOCK_OK(r);
		if (r != 0)
			return sm_error_perm(SM_EM_Q, r);
	}

	if (SSOCCFL_EMPTY(&ssocc_ctx->ssocc_fl_hd))
	{
		ssocc_entry = (ssocc_entry_P) sm_zalloc(sizeof(*ssocc_entry));
		if (ssocc_entry == NULL)
			goto enomem;
#if SSOCC_CHECK
		ssocc_entry->sm_magic = SM_SSOCCE_MAGIC;
#endif
	}
	else
	{
		ssocc_entry = SSOCCFL_FIRST(&ssocc_ctx->ssocc_fl_hd);
		SM_IS_SSOCCE(ssocc_entry);
		SSOCCFL_REMOVE(&ssocc_ctx->ssocc_fl_hd);
	}

	*pssocc_entry = ssocc_entry;
	if (thr_unl_no_err(locktype))
	{
		r = pthread_mutex_unlock(&ssocc_ctx->ssocc_mutex);
		if (r != 0)
		{
			/* XXX ??? COMPLAIN */
			return sm_error_perm(SM_EM_Q, r);
		}
	}
	return SM_SUCCESS;

  enomem:
	ret = sm_error_temp(SM_EM_Q, ENOMEM);

	/* clean up...? nothing to do right now */
	if (thr_unl_if_err(locktype))
		(void) pthread_mutex_unlock(&ssocc_ctx->ssocc_mutex);
	return ret;
}
#endif /* 0 */

/*
**  SSOCC_ENTRY_NEW -- get/create new SSOCC entry
**
**	Parameters:
**		ssocc_ctx -- SSOCC context
**		ipv4 -- IPv4 address of server (HACK)
**		pssocc_entry -- pointer to SSOCC entry (output)
**		locktype -- kind of locking
**
**	Returns:
**		usual sm_error code
*/

sm_ret_T
ssocc_entry_new(ssocc_ctx_P ssocc_ctx, ipv4_T ipv4, ssocc_entry_P *pssocc_entry, thr_lock_T locktype)
{
	sm_ret_T ret;
	int r;
	ssocc_entry_P ssocc_entry;
#if !SSOCC_RSC
	bht_entry_P bht_entry;
#endif

	SM_IS_SSOCC(ssocc_ctx);
	ssocc_entry = NULL;
	if (thr_lock_it(locktype))
	{
		r = pthread_mutex_lock(&ssocc_ctx->ssocc_mutex);
		SM_LOCK_OK(r);
		if (r != 0)
			return sm_error_perm(SM_EM_Q, r);
	}

QM_LEV_DPRINTF(5, (QM_DEBFP, "func=ssocc_entry_new, empty=%d\n", SSOCCFL_EMPTY(&ssocc_ctx->ssocc_fl_hd)));

	if (SSOCCFL_EMPTY(&ssocc_ctx->ssocc_fl_hd))
	{
		ssocc_entry = (ssocc_entry_P) sm_zalloc(sizeof(*ssocc_entry));
		if (ssocc_entry == NULL)
			goto enomem;
#if SSOCC_CHECK
		ssocc_entry->sm_magic = SM_SSOCCE_MAGIC;
#endif
	}
	else
	{
		ssocc_entry = SSOCCFL_FIRST(&ssocc_ctx->ssocc_fl_hd);
		SM_IS_SSOCCE(ssocc_entry);
		SSOCCFL_REMOVE(&ssocc_ctx->ssocc_fl_hd);
	}

	ssocc_entry->ssocce_srv_ipv4 = ipv4;	/* XXX HACK! */

#if 0
	ssocc_entry->ssocce_init_conc = 0;
	ssocc_entry->ssocce_cur_conc = 0;
	ssocc_entry->ssocce_max_conc = 0;
#endif

QM_LEV_DPRINTF(4, (QM_DEBFP, "func=ssocc_entry_new, ssocc_entry=%p, ipv4=%A\n", ssocc_entry, (ipv4_T) ipv4));

#if SSOCC_RSC
	ret = rsc_add(ssocc_ctx->ssocc_ht, true,
			(char *)&ssocc_entry->ssocce_srv_ipv4,
			sizeof(ssocc_entry->ssocce_srv_ipv4),
			ssocc_entry, NULL, THR_NO_LOCK);
#else /* SSOCC_RSC */
	ret = bht_add(ssocc_ctx->ssocc_ht,
			(char *)&ssocc_entry->ssocce_srv_ipv4,
			sizeof(ssocc_entry->ssocce_srv_ipv4),
			ssocc_entry, &bht_entry);
#endif /* SSOCC_RSC */
	if (sm_is_err(ret))
	{
QM_LEV_DPRINTF(1, (QM_DEBFP, "sev=ERROR, func=ssocc_entry_new, bht_add=%x\n", ret));
		goto error;
	}

	*pssocc_entry = ssocc_entry;
	if (thr_unl_no_err(locktype))
	{
		r = pthread_mutex_unlock(&ssocc_ctx->ssocc_mutex);
		if (r != 0)
		{
QM_LEV_DPRINTF(1, (QM_DEBFP, "sev=ERROR, func=ssocc_entry_new, unlock=%d", r));
			/* XXX ??? COMPLAIN */
			return sm_error_perm(SM_EM_Q, r);
		}
	}
	return ret;

  enomem:
	ret = sm_error_temp(SM_EM_Q, ENOMEM);
  error:
	if (ssocc_entry != NULL)
	{
		/*
		**  Don't call ssocc_entry_free() because it calls bht_rm()
		**  but the entry hasn't been added (that's the error cause!)
		**  Maybe free() the entry instead (esp. if it was allocated)?
		*/

		ssocc_entry->ssocce_srv_ipv4 = 0;
		ssocc_entry->ssocce_open_se = 0;
		ssocc_entry->ssocce_open_se_exc = 0;
		ssocc_entry->ssocce_last_conn = 0;
		SSOCCFL_PRE(&ssocc_ctx->ssocc_fl_hd, ssocc_entry);
		ssocc_entry = NULL;
	}

	/* clean up...? nothing to do right now */
	if (thr_unl_if_err(locktype))
	{
		(void) pthread_mutex_unlock(&ssocc_ctx->ssocc_mutex);
QM_LEV_DPRINTF(2, (QM_DEBFP, "func=ssocc_entry_new, err_unlock\n"));
	}
	return ret;
}

/*
**   SSOCC_ENTRY_FIND -- find a session in SSOCC by IPv4 address
**
**	Parameters:
**		ssocc_ctx -- SSOCC context
**		ipv4 -- IPv4 address
**		pssocc_entry -- pointer to SSOCC entry (output)
**		locktype -- kind of locking
**
**	Returns:
**		usual sm_error code; SM_E_NOTFOUND, (un)lock errors
*/

sm_ret_T
ssocc_entry_find(ssocc_ctx_P ssocc_ctx, ipv4_T ipv4, ssocc_entry_P *pssocc_entry, thr_lock_T locktype)
{
	sm_ret_T ret;
	int r;
	ssocc_entry_P ssocc_entry;

	SM_IS_SSOCC(ssocc_ctx);
	SM_REQUIRE(pssocc_entry != NULL);
	*pssocc_entry = NULL;

	if (thr_lock_it(locktype))
	{
		r = pthread_mutex_lock(&ssocc_ctx->ssocc_mutex);
		SM_LOCK_OK(r);
		if (r != 0)
			return sm_error_perm(SM_EM_Q, r);
	}
	ret = SM_SUCCESS;
#if SSOCC_RSC
	ssocc_entry = (ssocc_entry_P) rsc_lookup(ssocc_ctx->ssocc_ht,
				(const char *)&ipv4, sizeof(ipv4), THR_NO_LOCK);
#else
	ssocc_entry = (ssocc_entry_P) bht_find(ssocc_ctx->ssocc_ht,
					(const char *)&ipv4, sizeof(ipv4));
#endif

QM_LEV_DPRINTF(5, (QM_DEBFP, "func=ssocc_entry_find, entry=%p\n", ssocc_entry));

	if (ssocc_entry == NULL)
	{
		ret = sm_error_perm(SM_EM_Q, SM_E_NOTFOUND);
		goto error;
	}
	else
	{
		SM_IS_SSOCCE(ssocc_entry);
#if 0
		if (ssocc_entry->ssocce_cur_conc == 0
		    && ssocc_entry->ssocce_last_conn + SSOCCE_TO < time(NULLT))
			ssocc_entry->ssocce_cur_conc = ssocc_entry->ssocce_init_conc;
#endif /* 0 */
		*pssocc_entry = ssocc_entry;
	}
	if (thr_unl_no_err(locktype))
	{
		r = pthread_mutex_unlock(&ssocc_ctx->ssocc_mutex);
QM_LEV_DPRINTF(1, (QM_DEBFP, "func=ssocc_entry_find, unlock=%d\n", r));
		if (r != 0)
		{
			/* XXX ??? COMPLAIN */
			return sm_error_perm(SM_EM_Q, r);
		}
	}
	return ret;

  error:
	/* clean up...? nothing to do right now */
	if (thr_unl_if_err(locktype))
	{
		(void) pthread_mutex_unlock(&ssocc_ctx->ssocc_mutex);
QM_LEV_DPRINTF(1, (QM_DEBFP, "func=ssocc_entry_find, err_unlock\n"));
	}
	return ret;
}


syntax highlighted by Code2HTML, v. 0.9.1