/*
 * Copyright (c) 2002-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: dadb.c,v 1.78 2006/11/22 17:01:27 ca Exp $")
#include "sm/types.h"
#include "sm/assert.h"
#include "sm/magic.h"
#include "sm/str.h"
#include "sm/mta.h"
#include "sm/memops.h"
#include "sm/qmgr.h"
#include "sm/qmgr-int.h"
#include "sm/dadb.h"
#include "dadb.h"
#include "occ.h"

#include "sm/io.h"
#define LOCK_DPRINTF(x)

/*
**  Simple version of DA status database
**
**  dadb_open: We use an array since we know the maximum number of
**  "threads" in the DA. We use an index to access the DA data in that array.
**  We use that index when exchanging data with the DA: it is encoded
**  in the id; see include/sm/mta.h
**  see also queuemanager.func.tex: func:QMGR:DAStatusCache
*/


/*
**  DADB_SESS_OPEN -- open a session in DADB
**
**	Parameters:
**		qsc_ctx -- QMGR SMTPC context
**		dadb_ctx -- DADB context
**		ss_ta_id -- transaction id from SMTPS
**		srv_ipv4 -- IPv4 address of server (HACK)
**		aq_rcpt -- one recipient for a transaction (can be NULL)
**		pdadb_entry -- pointer to DA DB entry (output)
**
**	Returns:
**		usual sm_error code
**
**	Side Effects: none on error (except if unlock fails) but see below!
**
**	Locking: locks entire dadb_ctx during operation, returns unlocked
**
**	Last code review: 2005-03-13 02:59:57; see below (clean up)
**	Last code change:
*/

sm_ret_T
dadb_sess_open(qsc_ctx_P qsc_ctx, dadb_ctx_P dadb_ctx, sessta_id_P ss_ta_id, ipv4_T srv_ipv4, aq_rcpt_P aq_rcpt, dadb_entry_P *pdadb_entry)
{
#undef SMFCT
#define SMFCT "dadb_sess_open"
	sm_ret_T ret;
	uint idx, st_flags;
	int r;
	dadb_entry_P dadb_entry;
	occ_entry_P occ_entry;
	time_T time_now;

#define SM_D_FL_DADB_LOCKED	0x0001
#define SM_D_FL_DADBE_LOCKED	0x0002

	SM_IS_DADB(dadb_ctx);
	SM_REQUIRE(pdadb_entry != NULL);
	st_flags = 0;

	dadb_entry = NULL;
	occ_entry = NULL;

	ret = occ_sess_open(qsc_ctx->qsc_qmgr_ctx,
			qsc_ctx->qsc_qmgr_ctx->qmgr_occ_ctx, srv_ipv4,
			&occ_entry, THR_LOCK_UNLOCK);
	if (sm_is_err(ret))
		goto error;

	time_now = evthr_time(qsc_ctx->qsc_qmgr_ctx->qmgr_ev_ctx);
	r = pthread_mutex_lock(&dadb_ctx->dadb_mutex);
	SM_LOCK_OK(r);
	if (r != 0)
		return sm_error_perm(SM_EM_DA, r);
	SM_SET_FLAG(st_flags, SM_D_FL_DADB_LOCKED);
	if (!DADB_IS_OK(dadb_ctx)) {
		ret = sm_error_perm(SM_EM_DA, SM_E_UNAVAIL);
		goto error;
	}

	/* search for a free DA DB entry */
	ret = dadb_entry_get(dadb_ctx, &dadb_entry, &idx, THR_NO_LOCK);
	DADB_DPRINTF((smioerr, "sev=INFO, func=dadb_sess_open, dadb_find_free=%d, idx=%d\n", ret, idx));
	if (sm_is_err(ret)) /* (ret != SM_SUCCESS), see dadb_entry_get() */
		goto error;
	SM_SET_FLAG(st_flags, SM_D_FL_DADBE_LOCKED);

	dadb_entry->dadbe_lastuse = time_now;
	dadb_entry->dadbe_srv_ipv4 = srv_ipv4;

	/* relies on dadb_ctx->dadb_mutex! */
	ret = qsc_id_next(qsc_ctx, qsc_ctx->qsc_id, idx,
			dadb_entry->dadbe_da_se_id);
	if (sm_is_err(ret))
		goto error;
	ret = qsc_id_next(qsc_ctx, qsc_ctx->qsc_id, idx,
			dadb_entry->dadbe_da_ta_id);
	if (sm_is_err(ret))
		goto error;

#if 0
//	if (new) {
//		ret = bht2_add(&bht2e);
//		DADB_DPRINTF((smioerr, "func=dadb_sess_open, bht2_add=%x\n", ret));
//		if (sm_is_err(ret))
//			goto error;
//	}
#endif /* 0 */

	SESSTA_COPY(dadb_entry->dadbe_ss_ta_id, ss_ta_id);
	DADBE_SET_SE_OPEN(dadb_entry);
	dadb_entry->dadbe_rcpt = aq_rcpt;

#if DADBE_MUTEX
	r = pthread_mutex_unlock(&dadb_entry->dadbe_mutex);
	SM_ASSERT(0 == r);
	if (r != 0)
		ret = sm_error_perm(SM_EM_DA, r);
	else
		SM_CLR_FLAG(st_flags, SM_D_FL_DADBE_LOCKED);
#endif

	r = pthread_mutex_unlock(&dadb_ctx->dadb_mutex);
	SM_ASSERT(0 == r);
	if (r != 0)
		ret = sm_error_perm(SM_EM_DA, r);
	else
		SM_CLR_FLAG(st_flags, SM_D_FL_DADB_LOCKED);

	*pdadb_entry = dadb_entry;
	return ret;

  error:
	/* clean up?? */
	if (occ_entry != NULL) {
		/*
		**  fixme: this should be a function in occ.c
		**  This is broken for !DA_OCC_RSC because the entry isn't
		**  free()d; moreover, it's not clear whether the entry
		**  must be free()d because that information is not returned
		**  by occ_sess_open(). Could that be a flag in occ_entry?
		**  Set it when the entry is allocated, clear it when
		**  it is reused?
		**  Last, but not least: access to occ_entry isn't locked here.
		*/

		if (occ_entry->occe_open_se > 0)
			--occ_entry->occe_open_se;
		if (occ_entry->occe_open_ta > 0)
			--occ_entry->occe_open_ta;
	}
	if (dadb_entry != NULL) {
		/* mark entry as free */
		DADBE_CLR_FLAG(dadb_entry, DADBE_FL_BUSY|DADBE_FL_IDLE);
		dadb_entry->dadbe_da_se_id[0] = '\0';
		dadb_entry->dadbe_da_ta_id[0] = '\0';
		SM_ASSERT(dadb_ctx->dadb_entries_cur > 0);
		--dadb_ctx->dadb_entries_cur;
	}

	*pdadb_entry = NULL;
#if DADBE_MUTEX
	if (SM_IS_FLAG(st_flags, SM_D_FL_DADBE_LOCKED)) {
		r = pthread_mutex_unlock(&dadb_entry->dadbe_mutex);
		SM_ASSERT(0 == r);
		if (r != 0 && sm_is_success(ret))
			ret = sm_error_perm(SM_EM_DA, r);
	}
#endif
	if (SM_IS_FLAG(st_flags, SM_D_FL_DADB_LOCKED)) {
		r = pthread_mutex_unlock(&dadb_ctx->dadb_mutex);
		SM_ASSERT(0 == r);
		if (r != 0 && sm_is_success(ret))
			ret = sm_error_perm(SM_EM_DA, r);
	}
	return ret;
}

/*
**  DADB_SESS_REUSE -- reuse a session in DADB
**
**	Parameters:
**		qsc_ctx -- QMGR SMTPC context
**		dadb_ctx -- DADB context
**		ss_ta_id -- transaction id from SMTPS
**		dadb_entry -- DA DB entry to reuse
**		occ_entry -- OCC entry
**		aq_rcpt -- one recipient for a transaction (can be NULL)
**
**	Returns:
**		usual sm_error code; SM_E_UNEXPECTED
**
**	Side Effects: none on error (except if unlock fails)
**
**	Locking: locks entire dadb_ctx during operation, returns unlocked
**	occ_entry MUST be locked by caller (use a parameter instead?)
**
**	Last code review: 2005-03-16 05:23:53
**	Last code change:
*/

sm_ret_T
dadb_sess_reuse(qsc_ctx_P qsc_ctx, dadb_ctx_P dadb_ctx, sessta_id_P ss_ta_id, dadb_entry_P dadb_entry, occ_entry_P occ_entry, aq_rcpt_P aq_rcpt)
{
#undef SMFCT
#define SMFCT "dadb_sess_reuse"
	sm_ret_T ret;
	uint idx;
	int r;
	time_T time_now;

	SM_IS_DADB(dadb_ctx);
	time_now = evthr_time(qsc_ctx->qsc_qmgr_ctx->qmgr_ev_ctx);
	r = pthread_mutex_lock(&dadb_ctx->dadb_mutex);
	SM_LOCK_OK(r);
	if (r != 0)
		return sm_error_perm(SM_EM_DA, r);
	if (!DADB_IS_OK(dadb_ctx)) {
		ret = sm_error_perm(SM_EM_DA, SM_E_UNAVAIL);
		goto error;
	}

	r = SMTPC_GETTHRIDX(dadb_entry->dadbe_da_ta_id, idx);
	if (r != 1 || idx >= dadb_ctx->dadb_entries_max) {
		/* COMPLAIN */
		ret = sm_error_perm(SM_EM_DA, SM_E_UNEXPECTED);
		goto error;
	}

	if (dadb_entry->dadbe_lastuse > 0
	    && dadb_entry->dadbe_lastuse < time_now 
	    && time_now  - dadb_entry->dadbe_lastuse > SM_SESSION_TMO)
	{
		ret = sm_error_perm(SM_EM_DA, SM_E_EXPIRED);
		goto error;
	}

	ret = qsc_id_next(qsc_ctx, qsc_ctx->qsc_id, idx, dadb_entry->dadbe_da_ta_id);
	if (sm_is_err(ret))
		goto error;

	SESSTA_COPY(dadb_entry->dadbe_ss_ta_id, ss_ta_id);
	DADBE_SET_SE_OPEN(dadb_entry);
	dadb_entry->dadbe_lastuse = time_now;
	dadb_entry->dadbe_rcpt = aq_rcpt;

	/* doesn't fail */
	(void) occ_sess_reuse(qsc_ctx->qsc_qmgr_ctx->qmgr_occ_ctx, occ_entry,
			time_now, THR_NO_LOCK);
	r = pthread_mutex_unlock(&dadb_ctx->dadb_mutex);
	SM_ASSERT(0 == r);
	if (r != 0 && sm_is_success(ret))
		ret = sm_error_perm(SM_EM_DA, r);
	return SM_SUCCESS;

  error:
	r = pthread_mutex_unlock(&dadb_ctx->dadb_mutex);
	SM_ASSERT(0 == r);
	if (r != 0 && sm_is_success(ret))
		ret = sm_error_perm(SM_EM_DA, r);
	return ret;
}

/*
**  DADB_SESS_CLOSE_ENTRY -- close a session in DADB
**
**	Parameters:
**		qmgr_ctx -- QMGR context (only qmgr_conf is needed)
**		dadb_ctx -- DADB context
**		dadb_entry -- DA DB entry (session) to close
**		ok -- session was successful?
**		pflags -- flags of session (output, may be NULL)
**		locktype -- kind of locking
**
**	Returns:
**		usual sm_error code; SM_E_NOTFOUND, (un)lock errors
**
**	Side Effects:
**		reset ids in dadb_entry (even on error)
**
**	Locking: locks dadb_ctx if requested
**
**	Last code review: 2005-03-17 00:22:16
**	Last code change:
*/

sm_ret_T
dadb_sess_close_entry(qmgr_ctx_P qmgr_ctx, dadb_ctx_P dadb_ctx, dadb_entry_P dadb_entry, bool ok, uint32_t *pflags, thr_lock_T locktype)
{
#undef SMFCT
#define SMFCT "dadb_sess_close_entry"
	int r;
	sm_ret_T ret;

	SM_IS_DADB(dadb_ctx);
	SM_IS_DADBE(dadb_entry);

	ret = occ_sess_close_entry(qmgr_ctx->qmgr_occ_ctx,
			dadb_entry->dadbe_srv_ipv4, ok,
			evthr_time(qmgr_ctx->qmgr_ev_ctx), pflags, locktype);

	if (thr_lock_it(locktype)) {
		r = pthread_mutex_lock(&dadb_ctx->dadb_mutex);
		SM_LOCK_OK(r);
		if (r != 0)
			return sm_error_perm(SM_EM_DA, r);
	}
	if (DADB_IS_OK(dadb_ctx)) {
		/* paranoia... */
		dadb_entry->dadbe_da_se_id[0] = '\0';
		dadb_entry->dadbe_da_ta_id[0] = '\0';

		dadb_entry->dadbe_rcpt = NULL;
		if (DADBE_IS_FLAG(dadb_entry, DADBE_FL_IDLE)) {
			SM_ASSERT(dadb_ctx->dadb_entries_idle > 0);
			--dadb_ctx->dadb_entries_idle;
		}
		DADBE_CLR_FLAG(dadb_entry, DADBE_FL_BUSY|DADBE_FL_IDLE);
		SM_ASSERT(dadb_ctx->dadb_entries_cur > 0);
		if (pflags != NULL &&
		    dadb_ctx->dadb_entries_cur == dadb_ctx->dadb_entries_lim)
			*pflags |= DADBE_FL_AVAIL;
		--dadb_ctx->dadb_entries_cur;
	}
	if ((!sm_is_err(ret) && thr_unl_no_err(locktype))
	    || (sm_is_err(ret) && thr_unl_if_err(locktype)))
	{
		r = pthread_mutex_unlock(&dadb_ctx->dadb_mutex);
		SM_ASSERT(0 == r);
		if (r != 0 && sm_is_success(ret))
			ret = sm_error_perm(SM_EM_DA, r);
	}
	return ret;
}

/*
**  DADB_TA_CLOSE_ENTRY -- close a transaction in DADB
**
**	Parameters:
**		qmgr_ctx -- QMGR context
**		dadb_ctx -- DADB context
**		dadb_entry -- DA DB entry to close
**		pflags -- flags of session (output, may be NULL)
**		locktype -- kind of locking
**
**	Returns:
**		usual sm_error code; SM_E_NOTFOUND, (un)lock errors
**
**	Side Effects:
**		change flags in dadb_entry (even on error)
**
**	Locking: locks dadb_ctx if requested
**
**	Last code review: 2005-03-17 00:22:40
**	Last code change:
*/

sm_ret_T
dadb_ta_close_entry(qmgr_ctx_P qmgr_ctx, dadb_ctx_P dadb_ctx, dadb_entry_P dadb_entry, uint32_t *pflags, thr_lock_T locktype)
{
#undef SMFCT
#define SMFCT "dadb_ta_close_entry"
	int r;
	sm_ret_T ret;

	SM_IS_DADB(dadb_ctx);
	SM_IS_DADBE(dadb_entry);

	ret = occ_ta_close_entry(qmgr_ctx->qmgr_occ_ctx,
				dadb_entry->dadbe_srv_ipv4,
				evthr_time(qmgr_ctx->qmgr_ev_ctx),
				pflags, locktype);

	if (thr_lock_it(locktype)) {
		r = pthread_mutex_lock(&dadb_ctx->dadb_mutex);
		SM_LOCK_OK(r);
		if (r != 0)
			return sm_error_perm(SM_EM_DA, r);
	}
	if (DADB_IS_OK(dadb_ctx)) {
		dadb_entry->dadbe_rcpt = NULL;
		DADBE_SET_CONN(dadb_entry);
		++dadb_ctx->dadb_entries_idle;
	}
	if ((!sm_is_err(ret) && thr_unl_no_err(locktype))
	    || (sm_is_err(ret) && thr_unl_if_err(locktype)))
	{
		r = pthread_mutex_unlock(&dadb_ctx->dadb_mutex);
		SM_ASSERT(0 == r);
		if (r != 0 && sm_is_success(ret))
			ret = sm_error_perm(SM_EM_DA, r);
	}
	return ret;
}

/*
**  DADB_TA_FIND -- find a transaction in DADB
**
**	Parameters:
**		dadb_ctx -- DADB context
**		da_ta_id -- DA transaction id
**		pdadb_entry -- pointer to DA DB entry (output)
**
**	Returns:
**		usual sm_error code; SM_E_UNEXPECTED
**
**	Side Effects: none on error (except if unlock fails)
**
**	Locking: locks entire dadb_ctx during operation, returns unlocked
**
**	Last code review: 2005-03-17 00:22:54
**	Last code change:
*/

sm_ret_T
dadb_ta_find(dadb_ctx_P dadb_ctx, sessta_id_P da_ta_id, dadb_entry_P *pdadb_entry)
{
#undef SMFCT
#define SMFCT "dadb_ta_find"
	uint i;
	sm_ret_T ret;
	int r;
	dadb_entry_P dadb_entry;

	SM_IS_DADB(dadb_ctx);
	SM_REQUIRE(pdadb_entry != NULL);
	SM_REQUIRE(da_ta_id != NULL);

	/* do we really need to lock this?? */
	r = pthread_mutex_lock(&dadb_ctx->dadb_mutex);
	SM_LOCK_OK(r);
	if (r != 0)
		return sm_error_perm(SM_EM_DA, r);
	if (!DADB_IS_OK(dadb_ctx)) {
		ret = sm_error_perm(SM_EM_DA, SM_E_UNAVAIL);
		goto error;
	}

	ret = SM_SUCCESS;

	r = SMTPC_GETTHRIDX(da_ta_id, i);
	if (r != 1 || i >= dadb_ctx->dadb_entries_max) {
		/* COMPLAIN */
		ret = sm_error_perm(SM_EM_DA, SM_E_UNEXPECTED);
		goto error;
	}
	dadb_entry = (dadb_ctx->dadb_entries)[i];

	/* paranoia */
	if (NULL == dadb_entry || !SESSTA_EQ(dadb_entry->dadbe_da_ta_id, da_ta_id))
	{
		/* COMPLAIN */
		DADB_DPRINTF((smioerr, "sev=ERROR, func=dadb_ta_find, i=%u, found:da_ta=%s, wanted:da_ta=%s\n", i, NULL == dadb_entry ? "NULL" : dadb_entry->dadbe_da_ta_id, da_ta_id));
		ret = sm_error_perm(SM_EM_DA, SM_E_UNEXPECTED);
		goto error;
	}
	SM_IS_DADBE(dadb_entry);
	r = pthread_mutex_unlock(&dadb_ctx->dadb_mutex);
	SM_ASSERT(0 == r);
	if (r != 0 && sm_is_success(ret))
		ret = sm_error_perm(SM_EM_DA, r);

	*pdadb_entry = dadb_entry;
	return ret;

  error:
	r = pthread_mutex_unlock(&dadb_ctx->dadb_mutex);
	SM_ASSERT(0 == r);
	if (r != 0 && sm_is_success(ret))
		ret = sm_error_perm(SM_EM_DA, r);
	*pdadb_entry = NULL;
	return ret;
}

/*
**  DADB_SE_FIND -- find a session in DADB
**
**	Parameters:
**		dadb_ctx -- DADB context
**		da_se_id -- DA session id
**		pdadb_entry -- pointer to DA DB entry (output)
**
**	Returns:
**		usual sm_error code
**
**	Side Effects: none on error (except if unlock fails)
**
**	Locking: locks entire dadb_ctx during operation, returns unlocked
**
**	Last code review: 2005-03-14 17:15:12
**	Last code change:
*/

sm_ret_T
dadb_se_find(dadb_ctx_P dadb_ctx, sessta_id_P da_se_id, dadb_entry_P *pdadb_entry)
{
#undef SMFCT
#define SMFCT "dadb_se_find"
	uint i;
	sm_ret_T ret;
	int r;
	dadb_entry_P dadb_entry;

	SM_IS_DADB(dadb_ctx);
	SM_REQUIRE(pdadb_entry != NULL);

	/* do we really need to lock this?? */
	r = pthread_mutex_lock(&dadb_ctx->dadb_mutex);
	SM_LOCK_OK(r);
	if (r != 0)
		return sm_error_perm(SM_EM_DA, r);
	ret = SM_SUCCESS;

	r = SMTPC_GETTHRIDX(da_se_id, i);
	if (r != 1 || i >= dadb_ctx->dadb_entries_max) {
		/* COMPLAIN */
		ret = sm_error_perm(SM_EM_DA, SM_E_UNEXPECTED);
		goto errunl;
	}
	dadb_entry = (dadb_ctx->dadb_entries)[i];

	/* paranoia */
	if (NULL == dadb_entry || !SESSTA_EQ(dadb_entry->dadbe_da_se_id, da_se_id))
	{
		/* COMPLAIN */
		DADB_DPRINTF((smioerr, "sev=ERROR, func=dadb_se_find, i=%u, found da_se_id=%s, wanted da_se_id=%s\n", i, NULL == dadb_entry ? "NULL" : dadb_entry->dadbe_da_se_id, da_se_id));
		ret = sm_error_perm(SM_EM_DA, SM_E_UNEXPECTED);
		goto errunl;
	}
	SM_IS_DADBE(dadb_entry);
	r = pthread_mutex_unlock(&dadb_ctx->dadb_mutex);
	SM_ASSERT(0 == r);
	if (r != 0 && sm_is_success(ret))
		ret = sm_error_perm(SM_EM_DA, r);

	*pdadb_entry = dadb_entry;
	return ret;

  errunl:
	r = pthread_mutex_unlock(&dadb_ctx->dadb_mutex);
	SM_ASSERT(0 == r);
	if (r != 0 && sm_is_success(ret))
		ret = sm_error_perm(SM_EM_DA, r);
	*pdadb_entry = NULL;
	return ret;

}

/*
**  DADB_DESTROY -- free (destroy) a DADB
**
**	Parameters:
**		dadb_ctx -- DADB context
**
**	Returns:
**		SM_SUCCESS
**
**	Locking: none, destroys DADB (and hence lock)
**
**	Last code review: 2005-03-14 17:23:05
**	Last code change: 2005-03-14 17:22:53
*/

sm_ret_T
dadb_destroy(dadb_ctx_P dadb_ctx)
{
#undef SMFCT
#define SMFCT "dadb_destroy"
	if (NULL == dadb_ctx)
		return SM_SUCCESS;
	SM_IS_DADB(dadb_ctx);
	dadb_ctx->dadb_state = DADB_ST_STOP;
	if (dadb_ctx->dadb_entries != NULL) {
		uint i;
		dadb_entry_P dadb_entry;

		for (i = 0; i < dadb_ctx->dadb_entries_max; i++) {
			dadb_entry = (dadb_ctx->dadb_entries)[i];
			if (dadb_entry != NULL)
				dadb_entry_free(dadb_entry);
		}
		FREE_DADB_ENTRIES(dadb_ctx);
	}
	(void) pthread_mutex_destroy(&dadb_ctx->dadb_mutex);
#if DADB_CHECK
	dadb_ctx->sm_magic = SM_MAGIC_NULL;
#endif
	sm_free_size(dadb_ctx, sizeof(*dadb_ctx));
	return SM_SUCCESS;
}

/*
**  DADB_NEW -- create a new DADB
**
**	Parameters:
**		pdadb_ctx -- pointer to DADB context (output)
**		max_elems -- maximum number of elements in DADB
**
**	Returns:
**		usual sm_error code; ENOMEM, mutex_init
**
**	Side Effects: none on error
**
**	Last code review: 2005-03-17 00:24:36
**	Last code change: 2005-03-14 17:29:05
*/

sm_ret_T
dadb_new(dadb_ctx_P *pdadb_ctx, uint max_elems)
{
#undef SMFCT
#define SMFCT "dadb_new"
	int r;
	size_t size;
	sm_ret_T ret;
	dadb_ctx_P dadb_ctx;

	SM_REQUIRE(pdadb_ctx != NULL);
	SM_REQUIRE(max_elems > 0);
	size = 0;
	dadb_ctx = (dadb_ctx_P) sm_zalloc(sizeof(*dadb_ctx));
	if (NULL == dadb_ctx)
		return sm_error_temp(SM_EM_DA, ENOMEM);

	size = max_elems * sizeof(*dadb_ctx->dadb_entries);
	SM_ASSERT(size > max_elems && size > sizeof(*dadb_ctx->dadb_entries));
	dadb_ctx->dadb_entries = (dadb_entry_P *) sm_zalloc(size);
	if (NULL == dadb_ctx->dadb_entries) {
		ret = sm_error_temp(SM_EM_DA, ENOMEM);
		goto error;
	}
	r = pthread_mutex_init(&dadb_ctx->dadb_mutex, SM_PTHREAD_MUTEXATTR);
	if (r != 0) {
		ret = sm_error_perm(SM_EM_DA, r);
		goto error;
	}
	dadb_ctx->dadb_entries_max = max_elems;
	dadb_ctx->dadb_entries_lim = max_elems;
	dadb_ctx->dadb_state = DADB_ST_INIT;

#if DADB_CHECK
	dadb_ctx->sm_magic = SM_DADB_MAGIC;
#endif
	*pdadb_ctx = dadb_ctx;
	return SM_SUCCESS;

  error:
	SM_ASSERT(dadb_ctx != NULL);	/* just paranoia */
	if (dadb_ctx->dadb_entries != NULL) {
		/*
		**  NOTE: this cleanup code depends on the sequence above!
		**  If that is changed, this must be changed too.
		*/

		(void) pthread_mutex_destroy(&dadb_ctx->dadb_mutex);
		SM_ASSERT(size > 0);
		sm_free_size(dadb_ctx->dadb_entries, size);
	}
#if DADB_CHECK
	dadb_ctx->sm_magic = SM_MAGIC_NULL;
#endif
	sm_free_size(dadb_ctx, sizeof(*dadb_ctx));
	*pdadb_ctx = NULL;
	return ret;
}


syntax highlighted by Code2HTML, v. 0.9.1