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