/* notes {{{
 * This module attempts to provide reuse of OCIEnv
 * and storage for session pool(s).
 *
 * Note, configuration uses acronym "drcp", which is
 * somewhat misleading. DRCP stands for "connection pooling"
 * and DBD::Oracle offers "session pooling", which is
 * not "connection pooling" (that is also offered by Oracle).
 *
 * To make things complex DBD::Oracle offers 2 ways to
 * share connections between threads. Such sharing
 * requires that one thread does not use pointers
 * to data allocated in another thread. In perl,
 * each thread gets copy of perl interpreter, and each
 * thread has its own heap for memory allocation.
 * When the thread ends, that memory may get released,
 * which leads to problems if the memory is referenced
 * from another thread.
 *
 * It appears, that OCI uses itsown memory pool for
 * allocation, so the data allocated in one thread
 * is still accessible in another thread. At least
 * tests don't fail in such cases. There are
 * also OCI internal mutexes that protect access to
 * that memory. The good news, one may ask OCI to allocate
 * some memory for the caller. That is what this
 * module is using.
 *
 * When OCIEnv is created, it uses 3 parameters
 * that define the functionality of new OCIEnv.
 * Those are: mode, charset and ncharset. So, when
 * we need some OCIEnv we have to check for existing
 * ones that have matching parameters. If charset
 * or ncharset is 0, then current value from environment
 * is used (OCINlsEnvironmentVariableGet)
 *
 * The session pool is identified (in addition to
 * charsets and mode for OCIEnv) by dbname, user
 * and "rlb" (activity of load-balancer). Since all
 * sessions are "homogenous", Oracle won't check
 * password when new session is requested. So probably
 * it makes sense to check it here. So password
 * is also saved.
 *
 * This module does not use HV to find cached
 * handles because HV may need to allocate memory
 * from current thread, and that is not good. Still
 * it is very unlikely that someone uses thousands
 * of different pools/characters, so simple
 * running through doubly-linked list should be
 * fast enough (and maybe even faster) than hashing.
 *
 * Now, how long shall be cached all handles? As long
 * as there are users. Each open connection is a user.
 * Additionally every "driver"-object is a user.
 * The driver-object is allocated in each thread
 * that tries to connect to DB and is released when
 * thread ends.
 }}} */
#include "Oracle.h"

DBISTATE_DECLARE;

typedef struct llist_t llist_t;
/* small implementation for doubly-linked list {{{ */
struct llist_t {
    llist_t * left;
    llist_t * right;
};

#define llist_empty(list) ((list)->left == (list))

#define llist_init(lst) do{\
    llist_t * list = lst;\
    list->right = list->left = list;\
}while(0)

#define llist_is_initialized(list) ((list)->left != NULL)

#define llist_add(aleft, aright) do{\
    llist_t * old;\
    llist_t * left = aleft;\
    llist_t * right = aright;\
    old = left->right;\
    left->right = right;\
    old->left = right->left;\
    right->left = left;\
    old->left->right = old;\
}while(0)

#define llist_drop(ael) do{\
    llist_t * el = ael;\
    if(!llist_empty(el)) {\
        el->left->right = el->right;\
        el->right->left = el->left;\
        llist_init(el);\
    } \
}while(0)

#define llist_size(list) ({\
    llist_t * base = list;\
    int size = 0;\
    while(base->left != list) {\
        size++;\
        base = base->left;\
    }\
    size;\
})

// this is pointer to the left element in chain
#define llist_left(list) (list)->left

// this one is a pointer to the right element in chain
#define llist_right(list) (list)->right
/* }}} */

#if defined(USE_ITHREADS)
static perl_mutex mng_lock;
#endif
static llist_t mng_list;
static int dr_instances;
/* to get information about charsets we need these */
static OCIEnv * mng_env;
static OCIError * mng_err;

/*dbd_dr_globals_init{{{*/
void
dbd_dr_globals_init()
{
#if defined(USE_ITHREADS)
    dTHX;
    MUTEX_INIT(&mng_lock);
#endif
    // Ancient-Wizard:
    // I believe this list, being static, was intented to be shared by all threads!
    //  Additional comments in the code suggest this as well.
    // However as it was written every thread was nuking the setup of the last
    //  startng with a fresh empty list. Smells like a memory leak at best
    //  and a crash at worst. We received both.
    if ( llist_is_initialized(&mng_list) ) return;

    llist_init(&mng_list);
    dr_instances = 0;
    mng_env = NULL;
    mng_err = NULL;
}
/*}}}*/

struct box_st {
    llist_t lock;
    int refs; /* this shall be positive for OCIEnv and negativ for Session Pool */
};

struct env_box_st
{
    box_t base;
    OCIEnv * envhp;
    ub4 mode;
    ub2 cset;
    ub2 ncset;
};
typedef struct env_box_st env_box_t;

#ifdef ORA_OCI_112
struct pool_box_st{
    box_t base;
    env_box_t * env;
    OCISPool *poolhp;
    OCIError *errhp;
    OraText *name;
    ub4	name_len;
    ub2 pass_len;
    ub2 dbname_len;
    /* all text strings are stored below. The buffer
     * is long enough to contain dbname_len+user_len+pass_len + 4 bytes.
     * So pass is box->buf + 1;
     * dbname is box->buf + 2 + box->pass_len
     * user is box->buf + 3 + box->dbname_len + box->pass_len
     * First byte is 0 if RLB is not desired.
     */
    char buf[1];
};
typedef struct pool_box_st pool_box_t;
#endif

#define tracer(imp, dlvl, vlvl, ...) if \
    (DBIc_DBISTATE(imp)->debug >= (dlvl) || dbd_verbose >= (vlvl) )\
            PerlIO_printf(DBIc_LOGPIO(imp), __VA_ARGS__)

/* local_error{{{*/
static int
local_error(pTHX_ SV * h, const char * fmt, ...)
{
    va_list ap;
    SV * txt_sv = sv_newmortal();
    SV * code_sv = get_sv("DBI::stderr", 0);
    D_imp_xxh(h);
    if(code_sv == NULL)
    {
        code_sv  = sv_newmortal();
        sv_setiv(code_sv, 2000000000);
    }
    va_start(ap, fmt);
    sv_vsetpvf(txt_sv, fmt, &ap);
    va_end(ap);
    DBIh_SET_ERR_SV(h, imp_xxh, code_sv, txt_sv, &PL_sv_undef, &PL_sv_undef);
    return FALSE;
}
/*}}}*/
/* release_env{{{*/
static void
release_env(pTHX_ env_box_t * box)
{
    llist_drop(&box->base.lock);
    if(dbd_verbose >= 3)
        warn("Releasing OCIEnv %p\n", box->envhp);
    (void)OCIHandleFree(box->envhp, OCI_HTYPE_ENV);
}/*}}}*/

/* new_envhp_box{{{*/
static int
new_envhp_box(pTHX_ env_box_t ** slot, dblogin_info_t * ctrl)
{
    imp_dbh_t * imp_dbh = ctrl->imp_dbh;
    env_box_t * box;
    OCIEnv * envhp;
    sword status = OCIEnvNlsCreate(
            &envhp, ctrl->mode,
            0, NULL, NULL, NULL,
            sizeof(*box), (dvoid**)&box,
            imp_dbh->cset, imp_dbh->ncset
    );
    if (status != OCI_SUCCESS)
        return local_error(aTHX_ ctrl->dbh, "Failed to allocate OCIEnv");
    tracer(imp_dbh, 3, 3, "allocated new OCIEnv %p (cset %d, ncset %d, mode %d)\n",
            envhp, (int)imp_dbh->cset, (int)imp_dbh->ncset, (int)ctrl->mode);
    llist_init(&box->base.lock);
    box->envhp = envhp;
    box->base.refs = dr_instances;
    box->cset = imp_dbh->cset;
    box->ncset = imp_dbh->ncset;
    box->mode = ctrl->mode;
    llist_add(&mng_list, &box->base.lock);
    *slot = box;
    return TRUE;
}/*}}}*/
/* figure_out_charsets {{{*/
static int
figure_out_charsets(pTHX_ dblogin_info_t * ctrl)
{
    imp_dbh_t * imp_dbh = ctrl->imp_dbh;
    if(ctrl->cset != NULL)
    {
        imp_dbh->cset = OCINlsCharSetNameToId(
                mng_env, (OraText*)ctrl->cset
        );
        if(imp_dbh->cset == 0) return local_error(
                aTHX_ ctrl->dbh, "Invalid ora_charset '%s'", ctrl->cset
        );
    }
    else if(imp_dbh->cset == 0)
    {
        size_t rsize;
        sword status = OCINlsEnvironmentVariableGet(
            &imp_dbh->cset, 0, OCI_NLS_CHARSET_ID, 0, &rsize
        );
        if(status != OCI_SUCCESS || imp_dbh->cset == 0)
            return local_error(aTHX_ ctrl->dbh, "NLS_LANG appears to be invalid");
    }
    if(ctrl->ncset != NULL)
    {
        imp_dbh->ncset = OCINlsCharSetNameToId(
                mng_env, (OraText*)ctrl->ncset
        );
        if(imp_dbh->ncset == 0) return local_error(
                aTHX_ ctrl->dbh, "Invalid ora_ncharset '%s'", ctrl->ncset
        );
    }
    else if(imp_dbh->ncset == 0)
    {
        size_t rsize;
        sword status = OCINlsEnvironmentVariableGet(
            &imp_dbh->ncset, 0, OCI_NLS_NCHARSET_ID, 0, &rsize
        );
        if(status != OCI_SUCCESS || imp_dbh->ncset == 0)
            return local_error(aTHX_ ctrl->dbh, "NLS_NCHAR appears to be invalid");
    }

    return TRUE;
}/*}}}*/
/*find_env{{{*/
static env_box_t *
find_env(ub4 mode, ub2 cset, ub2 ncset)
{
    llist_t * base = llist_left(&mng_list);
    while(base != &mng_list)
    {
        env_box_t * box = (env_box_t *)base;
        if(box->base.refs > 0
                && box->mode == mode
                && box->ncset == ncset
                && box->cset == cset
          )
        {
            /* check if this handle is still valid */
            if(box->base.refs == dr_instances)
            {
                OCIError 	*errhp;
                sword status=OCIHandleAlloc(
                        box->envhp,
                        (dvoid**)&errhp,
                        OCI_HTYPE_ERROR, 0, NULL
                );
                if(status != OCI_SUCCESS)
                {
                    llist_drop(base);
                    OCIHandleFree(box->envhp, OCI_HTYPE_ENV);
                    return NULL;
                }
            }
            return box;
        }
        base = llist_left(base);
    }
    return NULL;
}
/*}}}*/
/* simple_connect {{{*/
static int
simple_connect(pTHX_ dblogin_info_t * ctrl)
{
    imp_dbh_t * imp_dbh = ctrl->imp_dbh;
    sword status;
    ub4 ulen, plen, ctype;

    tracer(imp_dbh, 3, 3, "using OCIEnv envhp:%p to connect\n", imp_dbh->envhp);

    OCIHandleAlloc_ok(
            imp_dbh, imp_dbh->envhp, &imp_dbh->errhp,
            OCI_HTYPE_ERROR, status
    );
    if(status != OCI_SUCCESS)
        return local_error(aTHX_ ctrl->dbh, "Failed to allocate OCIError\n");

    OCIHandleAlloc_ok(
            imp_dbh, imp_dbh->envhp, &imp_dbh->srvhp,
            OCI_HTYPE_SERVER, status
    );
    if(status != OCI_SUCCESS)
        return local_error(aTHX_ ctrl->dbh, "Failed to allocate OCIServer\n");

    OCIHandleAlloc_ok(
            imp_dbh, imp_dbh->envhp, &imp_dbh->svchp,
            OCI_HTYPE_SVCCTX, status
    );
    if(status != OCI_SUCCESS)
        return local_error(aTHX_ ctrl->dbh, "Failed to allocate OCISvcCtx\n");

    OCIHandleAlloc_ok(
            imp_dbh, imp_dbh->envhp, &imp_dbh->seshp,
            OCI_HTYPE_SESSION, status
    );
    if(status != OCI_SUCCESS)
        return local_error(aTHX_ ctrl->dbh, "Failed to allocate OCISession\n");

    OCIServerAttach_log_stat(imp_dbh, ctrl->dbname,OCI_DEFAULT, status);
    if (status != OCI_SUCCESS)
            return oci_error(ctrl->dbh, imp_dbh->errhp, status, "OCIServerAttach");

    OCIAttrSet_log_stat(
            imp_dbh, imp_dbh->svchp,
            OCI_HTYPE_SVCCTX,
            imp_dbh->srvhp,
            (ub4) 0, OCI_ATTR_SERVER, imp_dbh->errhp, status
    );
    if (status != OCI_SUCCESS) return oci_error(
            ctrl->dbh, imp_dbh->errhp, status, "OCIAttrSet OCI_HTYPE_SVCCTX"
    );
    plen = (ub4)strlen(ctrl->pwd);
    ulen = (ub4)strlen(ctrl->uid);
    if(plen == 0 && ulen == 0) ctype = OCI_CRED_EXT;
    else
    {
        ctype = OCI_CRED_RDBMS;
        OCIAttrSet_log_stat(
                imp_dbh, imp_dbh->seshp,
                OCI_HTYPE_SESSION,
                ctrl->uid, ulen,
                (ub4) OCI_ATTR_USERNAME,
                imp_dbh->errhp, status
        );
        if (status != OCI_SUCCESS) return oci_error(
                ctrl->dbh, imp_dbh->errhp, status, "OCIAttrSet OCI_ATTR_USERNAME"
        );

        OCIAttrSet_log_stat(
                imp_dbh, imp_dbh->seshp,
                OCI_HTYPE_SESSION,
                ((plen) ? ctrl->pwd : NULL), plen,
                (ub4) OCI_ATTR_PASSWORD,
                imp_dbh->errhp, status
        );
        if (status != OCI_SUCCESS) return oci_error(
                ctrl->dbh, imp_dbh->errhp, status, "OCIAttrSet OCI_ATTR_PASSWORD"
        );
    }
#ifdef ORA_OCI_112
    if(ctrl->driver_name_len != 0)
    {
        OCIAttrSet_log_stat(
            imp_dbh, imp_dbh->seshp, OCI_HTYPE_SESSION,
            ctrl->driver_name, ctrl->driver_name_len,
            OCI_ATTR_DRIVER_NAME,
            imp_dbh->errhp, status
        );
        if (status != OCI_SUCCESS) return oci_error(
                ctrl->dbh, imp_dbh->errhp, status, "OCIAttrSet OCI_ATTR_DRIVER_NAME"
        );
    }
#endif

    OCISessionBegin_log_stat(
            imp_dbh, imp_dbh->svchp, imp_dbh->errhp, imp_dbh->seshp,
            ctype, ctrl->session_mode, status
    );
    if (status == OCI_SUCCESS_WITH_INFO)
    {
        /* eg ORA-28011: the account will expire soon; change your password now */
        oci_error(ctrl->dbh, imp_dbh->errhp, status, "OCISessionBegin");
        status = OCI_SUCCESS;
    }
    if (status != OCI_SUCCESS)
        return oci_error(ctrl->dbh, imp_dbh->errhp, status, "OCISessionBegin");

    OCIAttrSet_log_stat(
            imp_dbh, imp_dbh->svchp,
            (ub4) OCI_HTYPE_SVCCTX,
            imp_dbh->seshp, (ub4) 0,
            (ub4) OCI_ATTR_SESSION,
            imp_dbh->errhp, status
    );
    if (status != OCI_SUCCESS) return oci_error(
            ctrl->dbh, imp_dbh->errhp, status, "OCIAttrSet OCI_ATTR_SESSION"
    );
    return TRUE;
}/*}}}*/
#ifdef ORA_OCI_112
/*release_pool{{{*/
static void
release_pool(pTHX_ pool_box_t * box)
{
    env_box_t * ebox;
    if(dbd_verbose >= 3)
        warn("Releasing pool %p\n", box->poolhp);

    ebox = box->env;
    llist_drop(&box->base.lock);
    (void)OCISessionPoolDestroy (box->poolhp, box->errhp, OCI_DEFAULT);
    (void)OCIHandleFree(box->poolhp, OCI_HTYPE_SPOOL);
    ebox->base.refs--;
    if(ebox->base.refs == 0) release_env(aTHX_ ebox);
}/*}}}*/
/*cnx_pool_min{{{*/
void
cnx_pool_min(pTHX_ SV * dbh, imp_dbh_t * imp_dbh, ub4 val)
{
    pool_box_t * box;
    sword status;
    if(imp_dbh->lock->refs > 0) return;
    box = ((pool_box_t*)imp_dbh->lock);
    status = OCIAttrSet(
            box->poolhp, OCI_HTYPE_SPOOL,
            (dvoid*)&val, sizeof(val),
            OCI_ATTR_SPOOL_MIN, box->errhp
    );
    if(status != OCI_SUCCESS)
        (void)oci_error(dbh, box->errhp, status, "OCIAttrSet POOL_MIN");
}/*}}}*/
/*cnx_pool_max{{{*/
void
cnx_pool_max(pTHX_ SV * dbh, imp_dbh_t * imp_dbh, ub4 val)
{
    pool_box_t * box;
    sword status;
    if(imp_dbh->lock->refs > 0) return;
    box = ((pool_box_t*)imp_dbh->lock);
    status = OCIAttrSet(
            box->poolhp, OCI_HTYPE_SPOOL,
            (dvoid*)&val, sizeof(val),
            OCI_ATTR_SPOOL_MAX, box->errhp
    );
    if(status != OCI_SUCCESS)
        (void)oci_error(dbh, box->errhp, status, "OCIAttrSet POOL_MAX");
}/*}}}*/
/*cnx_pool_incr{{{*/
void
cnx_pool_incr(pTHX_ SV * dbh, imp_dbh_t * imp_dbh, ub4 val)
{
    pool_box_t * box;
    sword status;
    if(imp_dbh->lock->refs > 0) return;
    box = ((pool_box_t*)imp_dbh->lock);
    status = OCIAttrSet(
            box->poolhp, OCI_HTYPE_SPOOL,
            (dvoid*)&val, sizeof(val),
            OCI_ATTR_SPOOL_INCR, box->errhp
    );
    if(status != OCI_SUCCESS)
        (void)oci_error(dbh, box->errhp, status, "OCIAttrSet POOL_INCR");
}/*}}}*/
/*cnx_pool_mode{{{*/
void
cnx_pool_mode(pTHX_ SV * dbh, imp_dbh_t * imp_dbh, ub4 val)
{
    pool_box_t * box;
    sword status;
    if(imp_dbh->lock->refs > 0) return;
    box = ((pool_box_t*)imp_dbh->lock);
    status = OCIAttrSet(
            box->poolhp, OCI_HTYPE_SPOOL,
            (dvoid*)&val, sizeof(val),
            OCI_ATTR_SPOOL_GETMODE, box->errhp
    );
    if(status != OCI_SUCCESS)
        (void)oci_error(dbh, box->errhp, status, "OCIAttrSet POOL_GETMODE");
}/*}}}*/
/*cnx_pool_wait{{{*/
#if OCI_MAJOR_VERSION > 18
void
cnx_pool_wait(pTHX_ SV * dbh, imp_dbh_t * imp_dbh, ub4 val)
{
    pool_box_t * box;
    sword status;
    if(imp_dbh->lock->refs > 0) return;
    box = ((pool_box_t*)imp_dbh->lock);
    status = OCIAttrSet(
            box->poolhp, OCI_HTYPE_SPOOL,
            (dvoid*)&val, sizeof(val),
            OCI_ATTR_SPOOL_WAIT_TIMEOUT, box->errhp
    );
    if(status != OCI_SUCCESS)
        (void)oci_error(dbh, box->errhp, status, "OCIAttrSet POOL_WAIT_TIMEOUT");
}
#endif
/*}}}*/
/*cnx_is_pooled_session{{{*/
int
cnx_is_pooled_session(pTHX_ SV *dbh, imp_dbh_t * imp_dbh)
{
    return (imp_dbh->lock->refs < 0);
}/*}}}*/
/*cnx_get_pool_mode{{{*/
int
cnx_get_pool_mode(pTHX_ SV *dbh, imp_dbh_t * imp_dbh)
{
    pool_box_t * box;
    ub4 v, l;
    sword status;
    if(imp_dbh->lock->refs > 0) return 0;
    box = ((pool_box_t*)imp_dbh->lock);
    status = OCIAttrGet(
            box->poolhp, OCI_HTYPE_SPOOL,
            (dvoid*)&v, &l,
            OCI_ATTR_SPOOL_GETMODE, box->errhp
    );
    if(status == OCI_SUCCESS) return (int)v;
    (void)oci_error(dbh, box->errhp, status, "OCIAttrGet POOL_METHOD");
    return 0;
}/*}}}*/
/*cnx_get_pool_wait{{{*/
#if OCI_MAJOR_VERSION > 18
int
cnx_get_pool_wait(pTHX_ SV *dbh, imp_dbh_t * imp_dbh)
{
    pool_box_t * box;
    ub4 v, l;
    sword status;
    if(imp_dbh->lock->refs > 0) return 0;
    box = ((pool_box_t*)imp_dbh->lock);
    status = OCIAttrGet(
            box->poolhp, OCI_HTYPE_SPOOL,
            (dvoid*)&v, &l,
            OCI_ATTR_SPOOL_WAIT_TIMEOUT, box->errhp
    );
    if(status == OCI_SUCCESS) return (int)v;
    (void)oci_error(dbh, box->errhp, status, "OCIAttrGet POOL_WAIT_TIMEOUT");
    return 0;
}
#endif
/*}}}*/
/*cnx_get_pool_used{{{*/
int
cnx_get_pool_used(pTHX_ SV *dbh, imp_dbh_t * imp_dbh)
{
    pool_box_t * box;
    ub4 v, l;
    sword status;
    if(imp_dbh->lock->refs > 0) return 0;
    box = ((pool_box_t*)imp_dbh->lock);
    status = OCIAttrGet(
            box->poolhp, OCI_HTYPE_SPOOL,
            (dvoid*)&v, &l,
            OCI_ATTR_SPOOL_BUSY_COUNT, box->errhp
    );
    if(status == OCI_SUCCESS) return (int)v;
    (void)oci_error(dbh, box->errhp, status, "OCIAttrGet POOL_USED");
    return 0;
}/*}}}*/
/*cnx_get_pool_max{{{*/
int
cnx_get_pool_max(pTHX_ SV *dbh, imp_dbh_t * imp_dbh)
{
    pool_box_t * box;
    ub4 v, l;
    sword status;
    if(imp_dbh->lock->refs > 0) return 0;
    box = ((pool_box_t*)imp_dbh->lock);
    status = OCIAttrGet(
            box->poolhp, OCI_HTYPE_SPOOL,
            (dvoid*)&v, &l,
            OCI_ATTR_SPOOL_MAX, box->errhp
    );
    if(status == OCI_SUCCESS) return (int)v;
    (void)oci_error(dbh, box->errhp, status, "OCIAttrGet POOL_MAX");
    return 0;
}/*}}}*/
/*cnx_get_pool_min{{{*/
int
cnx_get_pool_min(pTHX_ SV *dbh, imp_dbh_t * imp_dbh)
{
    pool_box_t * box;
    ub4 v, l;
    sword status;
    if(imp_dbh->lock->refs > 0) return 0;
    box = ((pool_box_t*)imp_dbh->lock);
    status = OCIAttrGet(
            box->poolhp, OCI_HTYPE_SPOOL,
            (dvoid*)&v, &l,
            OCI_ATTR_SPOOL_MIN, box->errhp
    );
    if(status == OCI_SUCCESS) return (int)v;
    (void)oci_error(dbh, box->errhp, status, "OCIAttrGet POOL_MIN");
    return 0;
}/*}}}*/
/*cnx_get_pool_incr{{{*/
int
cnx_get_pool_incr(pTHX_ SV *dbh, imp_dbh_t * imp_dbh)
{
    pool_box_t * box;
    ub4 v, l;
    sword status;
    if(imp_dbh->lock->refs > 0) return 0;
    box = ((pool_box_t*)imp_dbh->lock);
    status = OCIAttrGet(
            box->poolhp, OCI_HTYPE_SPOOL,
            (dvoid*)&v, &l,
            OCI_ATTR_SPOOL_INCR, box->errhp
    );
    if(status == OCI_SUCCESS) return (int)v;
    (void)oci_error(dbh, box->errhp, status, "OCIAttrGet POOL_INCR");
    return 0;
}/*}}}*/
/*cnx_get_pool_rlb{{{*/
int
cnx_get_pool_rlb(pTHX_ SV *dbh, imp_dbh_t * imp_dbh)
{
    if(imp_dbh->lock->refs > 0) return 0;
    return (int)(((pool_box_t*)imp_dbh->lock)->buf[0]);
}/*}}}*/
/*find_pool{{{*/
static pool_box_t *
find_pool(dblogin_info_t * ctrl)
{
    imp_dbh_t * imp_dbh = ctrl->imp_dbh;
    llist_t * base = llist_left(&mng_list);
    while(base != &mng_list)
    {
        char * name;
        pool_box_t * box = (pool_box_t *)base;
        base = llist_left(base);
        if(box->base.refs > 0) continue;
        if(box->env->mode != ctrl->mode) continue;
        if(box->env->ncset != imp_dbh->ncset) continue;
        if(box->env->cset != imp_dbh->cset) continue;
        name = box->buf + 2 + box->pass_len;
        if(0 != strcmp(name, ctrl->dbname)) continue;
        name += box->dbname_len + 1;
        if(0 != strcmp(name, ctrl->uid)) continue;
        if((int)(box->buf[0]) != (int)(ctrl->pool_rlb)) continue;
        return box;
    }
    return NULL;
}
/*}}}*/
/* new_pool_box{{{*/
int
new_pool_box(pTHX_ pool_box_t ** slot, dblogin_info_t * ctrl)
{
    imp_dbh_t * imp_dbh = ctrl->imp_dbh;
    OCIEnv * envhp = imp_dbh->envhp;
    pool_box_t * box;
    char * name;
    OCISPool *poolhp;
    ub4 mode;
    ub4 dbname_len = strlen(ctrl->dbname);
    ub4 user_len = strlen(ctrl->uid);
    ub4 pwd_len = strlen(ctrl->pwd);
    size_t boxlen = sizeof(*box) + dbname_len + user_len + pwd_len + 3;

    sword status = OCIHandleAlloc(
            envhp, (dvoid*)&poolhp, OCI_HTYPE_SPOOL, boxlen, (dvoid**)&box
    );
    if (status != OCI_SUCCESS)
        return local_error(aTHX_ ctrl->dbh, "Failed to allocate OCISPool");

    status = OCIHandleAlloc(envhp, (dvoid*)&box->errhp, OCI_HTYPE_ERROR, 0, NULL);
    if (status != OCI_SUCCESS)
    {
        OCIHandleFree(poolhp, OCI_HTYPE_SPOOL);
        return local_error(aTHX_ ctrl->dbh, "Failed to allocate OCIError");
    }
#ifdef ORA_OCI_112
    if(ctrl->driver_name_len != 0)
    {
        OCIAuthInfo * authp;
        status = OCIHandleAlloc(envhp, (dvoid*)&authp, OCI_HTYPE_AUTHINFO, 0, NULL);
        if(status != OCI_SUCCESS)
        {
            OCIHandleFree(box->errhp, OCI_HTYPE_ERROR);
            OCIHandleFree(poolhp, OCI_HTYPE_SPOOL);
            return local_error(aTHX_ ctrl->dbh, "Failed to allocate OCIAuthInfo");
        }
        status = OCIAttrSet(
            authp, OCI_HTYPE_AUTHINFO,
            (OraText*)ctrl->driver_name, ctrl->driver_name_len,
            OCI_ATTR_DRIVER_NAME,
            box->errhp
        );
        if(status != OCI_SUCCESS)
        {
            (void)oci_error(ctrl->dbh, box->errhp, status, "OCIAttrSet DriverName");
            OCIHandleFree(box->errhp, OCI_HTYPE_ERROR);
            OCIHandleFree(authp, OCI_HTYPE_AUTHINFO);
            OCIHandleFree(poolhp, OCI_HTYPE_SPOOL);
            return FALSE;
        }
        status = OCIAttrSet(
            poolhp, OCI_HTYPE_SPOOL,
            authp, 0,
            OCI_ATTR_SPOOL_AUTH,
            box->errhp
        );
        if(status != OCI_SUCCESS)
        {
            (void)oci_error(ctrl->dbh, box->errhp, status, "OCIAttrSet OCIAuthInfo");
            OCIHandleFree(box->errhp, OCI_HTYPE_ERROR);
            OCIHandleFree(authp, OCI_HTYPE_AUTHINFO);
            OCIHandleFree(poolhp, OCI_HTYPE_SPOOL);
            return FALSE;
        }
        OCIHandleFree(authp, OCI_HTYPE_AUTHINFO);
    }
#endif


    mode = OCI_SPC_HOMOGENEOUS;
    if(!ctrl->pool_rlb) mode |= OCI_SPC_NO_RLB;
    status = OCISessionPoolCreate(
            envhp,
            box->errhp,
            poolhp,
            &box->name,
            &box->name_len,
            (OraText *) ctrl->dbname,
            dbname_len,
            ctrl->pool_min,
            ctrl->pool_max,
            ctrl->pool_incr,
            (OraText *) ctrl->uid,
            user_len,
            (OraText *) ctrl->pwd,
            pwd_len,
            mode
    );
    if (status != OCI_SUCCESS)
    {
        (void)oci_error(ctrl->dbh, box->errhp, status, "OCISessionPoolCreate");
        OCIHandleFree(box->errhp, OCI_HTYPE_ERROR);
        OCIHandleFree(poolhp, OCI_HTYPE_SPOOL);
        return FALSE;
    }
    llist_init(&box->base.lock);
    box->env = (env_box_t *)imp_dbh->lock;
    box->buf[0] = ctrl->pool_rlb;
    name = box->buf + 1;
    strcpy(name, ctrl->pwd);
    box->pass_len = pwd_len;
    name += pwd_len + 1;
    strcpy(name, ctrl->dbname);
    box->dbname_len = dbname_len;
    name += dbname_len + 1;
    strcpy(name, ctrl->uid);

    box->base.refs = -dr_instances;
    llist_add(&mng_list, &box->base.lock);
    box->poolhp = poolhp;

    *slot = box;
    return TRUE;
}/*}}}*/
/* session_from_pool {{{*/
static int
session_from_pool(pTHX_ dblogin_info_t * ctrl)
{
    imp_dbh_t * imp_dbh = ctrl->imp_dbh;
    sword status;
    OCIAuthInfo *authp;
    OraText *rettag;
    OraText *tag;
    ub4 rettagl;
    STRLEN tagl;
    boolean session_tag_found;
    /* try to find existing pool */
    pool_box_t * box = find_pool(ctrl);
    if(box != NULL)
    {
        if(0 != strcmp(box->buf + 1, ctrl->pwd))
            return local_error(aTHX_ ctrl->dbh, "Password for session is wrong");
    }
    else if(!new_pool_box(aTHX_ &box, ctrl)) return FALSE;

    /* replace env-box with pool-box. It shall be cleared by DESTROY */
    imp_dbh->lock = &box->base;
    box->base.refs--; /* refcount for pool is negative! I know, I know... */

    OCIHandleAlloc_ok(
        imp_dbh, imp_dbh->envhp, &imp_dbh->errhp, OCI_HTYPE_ERROR,  status
    );
    if(status != OCI_SUCCESS)
        return local_error(aTHX_ ctrl->dbh, "OCIError alloc failed");

    OCIHandleAlloc_ok(
        imp_dbh, imp_dbh->envhp, &authp, OCI_HTYPE_AUTHINFO, status
    );
    if(status != OCI_SUCCESS)
        return local_error(aTHX_ ctrl->dbh, "OCIAuthInfo alloc failed");
    if(ctrl->pool_class != NULL)
    {
        tag = (OraText*)SvPV(ctrl->pool_class, tagl);
        OCIAttrSet_log_stat(
                imp_dbh, authp,
                OCI_HTYPE_AUTHINFO,
                tag, (ub4) tagl,
                OCI_ATTR_CONNECTION_CLASS, imp_dbh->errhp, status
        );
        if(status != OCI_SUCCESS) return local_error(aTHX_ ctrl->dbh,
                "OCIAuthInfo setting connection class failed");
    }

    tag = NULL;
    tagl = 0;
    if(ctrl->pool_tag != NULL)
    {
        tag = (OraText*)SvPV(ctrl->pool_tag, tagl);
        if(tagl == 0) tag = NULL;
    }

    OCISessionGet_log_stat(
            imp_dbh, imp_dbh->envhp,
            imp_dbh->errhp,
            &imp_dbh->svchp,
            authp,
            box->name, box->name_len,
            tag, (ub4)tagl,
            &rettag, &rettagl, &session_tag_found,
            status
    );
    if (status != OCI_SUCCESS)
    {
        (void)oci_error(ctrl->dbh, imp_dbh->errhp, status, "OCISessionGet");
        OCIHandleFree_log_stat(imp_dbh, authp, OCI_HTYPE_AUTHINFO, status);
        return FALSE;
    }
    if(session_tag_found && rettagl != 0)
    {
        if(imp_dbh->session_tag != NULL) SvREFCNT_dec(imp_dbh->session_tag);
        imp_dbh->session_tag = newSVpv((char*)rettag, rettagl);
    }

    OCIHandleFree_log_stat(imp_dbh, authp, OCI_HTYPE_AUTHINFO, status);

    /* Get server and session handles from service context handle,
     * allocated by OCISessionGet. */
    OCIAttrGet_log_stat(
            imp_dbh, imp_dbh->svchp, OCI_HTYPE_SVCCTX, &imp_dbh->srvhp, NULL,
            OCI_ATTR_SERVER, imp_dbh->errhp, status
    );
    if(status != OCI_SUCCESS)
        return oci_error(ctrl->dbh, imp_dbh->errhp, status, "Server get");
    OCIAttrGet_log_stat(
            imp_dbh, imp_dbh->svchp, OCI_HTYPE_SVCCTX, &imp_dbh->seshp, NULL,
            OCI_ATTR_SESSION, imp_dbh->errhp, status
    );
    if(status != OCI_SUCCESS)
        return oci_error(ctrl->dbh, imp_dbh->errhp, status, "Session get");

    return 1;
}
/*}}}*/
#endif

#if defined(USE_ITHREADS)
static int _cnx_establish
#else
int cnx_establish
#endif
(pTHX_ dblogin_info_t * ctrl)
{
    imp_dbh_t * imp_dbh = ctrl->imp_dbh;
    env_box_t * env_box = NULL;

    if(!figure_out_charsets(aTHX_ ctrl)) return FALSE;
    /* try to find existing OCIEnv */
    env_box = find_env(ctrl->mode, imp_dbh->cset, imp_dbh->ncset);
    if(env_box == NULL && !new_envhp_box(aTHX_ &env_box, ctrl)) return FALSE;

    env_box->base.refs++;
    imp_dbh->envhp = env_box->envhp;
    imp_dbh->lock = &env_box->base;
    imp_dbh->errhp = NULL;
    imp_dbh->srvhp = NULL;
    imp_dbh->svchp = NULL;
    imp_dbh->seshp = NULL;
    /* Now I want DESTROY to be called if something goes wrong */
    DBIc_IMPSET_on(imp_dbh);
#ifdef ORA_OCI_112
    if(ctrl->pool_max != 0)
        return session_from_pool(aTHX_ ctrl);
#endif
    return simple_connect(aTHX_ ctrl);
}

#if defined(USE_ITHREADS)
int
cnx_establish(pTHX_ dblogin_info_t * ctrl)
{
    int rv;
    MUTEX_LOCK(&mng_lock);
    rv = _cnx_establish(aTHX_ ctrl);
    MUTEX_UNLOCK(&mng_lock);
    return rv;
}
#endif

/*dbd_dr_mng{{{
 * this function is called every time new DR object is created.
 * It is responsible for incrementing refs on all cached objects
 */
void
dbd_dr_mng()
{
    llist_t * el;
#if defined(USE_ITHREADS)
    dTHX;
    MUTEX_LOCK(&mng_lock);
#endif
    dr_instances++;
    el = llist_left(&mng_list);
    while(el != &mng_list)
    {
        box_t * base = (box_t *)el;
#ifdef ORA_OCI_112
        if(base->refs < 0) base->refs--;
        else
#endif
        base->refs++;
        el = llist_left(el);
    }
    if(mng_env == NULL)
    {
        // Now that this env handle is actually shared by threads the safest setup uses OCI_THREADED
        ub4 oci_mode = OCI_DEFAULT;
        // I haven't been able to prove that this is needed (TBD)
        // I'm still chasing another SEGV in an unknown call as of yet!
        // #if defined(USE_ITHREADS)
        // oci_mode |= OCI_THREADED;
        // #endif
        sword status = OCIEnvNlsCreate(
                &mng_env, oci_mode,
                0, NULL, NULL, NULL,
                0, NULL, 0, 0
        );
        if(status == OCI_SUCCESS)
            status=OCIHandleAlloc(
                    mng_env,
                    (dvoid**)&mng_err,
                    OCI_HTYPE_ERROR,
                    0, NULL
            );
        utf8_csid	   = OCINlsCharSetNameToId(mng_env, (void*)"UTF8");
        al32utf8_csid  = OCINlsCharSetNameToId(mng_env, (void*)"AL32UTF8");
        al16utf16_csid = OCINlsCharSetNameToId(mng_env, (void*)"AL16UTF16");
    }
#if defined(USE_ITHREADS)
    MUTEX_UNLOCK(&mng_lock);
#endif
}
/*}}}*/

/* cnx_detach{{{*/
void
cnx_detach(pTHX_ imp_dbh_t * imp_dbh)
{
    /* Oracle will commit on an orderly disconnect.	*/
    /* See DBI Driver.xst file for the DBI approach.	*/
#ifdef ORA_OCI_112
    if (imp_dbh->lock->refs < 0)
    {
        /* Release session, tagged for future retrieval. */
        if(imp_dbh->session_tag != NULL)
        {
            STRLEN tlen;
            char * tag = SvPV(imp_dbh->session_tag, tlen);
            (void)OCISessionRelease(
                    imp_dbh->svchp, imp_dbh->errhp,
                    (OraText*)tag, (ub4)tlen, OCI_SESSRLS_RETAG
            );
            SvREFCNT_dec(imp_dbh->session_tag);
            imp_dbh->session_tag = NULL;
        }
        else (void)OCISessionRelease(
                imp_dbh->svchp, imp_dbh->errhp,
                NULL, 0, OCI_DEFAULT
        );
        /* all handles are gone now */
        imp_dbh->seshp = NULL;
        imp_dbh->svchp = NULL;
        imp_dbh->srvhp = NULL;
    }
    else {
#endif

        sword status = OCI_SUCCESS;

        // GLOBAL Perl END's may cause these to SEGV due to filehandles (just a guess).
        //  Also forking can cause duplicate closings of a session. A preexisting handle in
        //  the parent is duplicated in the child. The first process to call SessionEnd
        //  wins, each successive call returns OCI_ERROR.
        //
        // The prior author included a comment about errors but was not specific and choose to ignore them.
        //
        // I thought I was being clever by adding the logging to these OCI calls;
        //  to help debug the original SEGV issue. Best guess is the logging relies on
        //  the filehandles being valid and they are not always valid, hence the SEGV
        //  within Perl's IO printing.
        //
        // The OCI calls are successful but the logging is not. I don't like silent failures
        //  so we'll use a more direct approach to handle this; keeping Perl and it's state
        //  out of the picture.

        // OCISessionEnd_log_stat( imp_dbh, imp_dbh->svchp, imp_dbh->errhp, imp_dbh->seshp, OCI_DEFAULT, status );
        // OCIServerDetach_log_stat( imp_dbh, imp_dbh->srvhp, imp_dbh->errhp, OCI_DEFAULT, status );

        if (( status = OCISessionEnd( imp_dbh->svchp, imp_dbh->errhp, imp_dbh->seshp, OCI_DEFAULT )) != OCI_SUCCESS
           && status != OCI_ERROR )
        {
            // by printing the pointers I could see the children and parent were
            //  using the same handles. The first is successful, the rest fail. (seems reasonable enough)
            fprintf( stderr, "OCISessionEnd() failed: %d %p %p %p pid=%d\n", status, imp_dbh->svchp, imp_dbh->errhp, imp_dbh->seshp, getpid());
        }

        if (( status = OCIServerDetach( imp_dbh->srvhp, imp_dbh->errhp, OCI_DEFAULT )) != OCI_SUCCESS )
        {
            fprintf( stderr, "OCIServerDetach() failed: %d pid=%d\n", status, getpid());
        }

#ifdef ORA_OCI_112
    }
#endif
}/*}}}*/
/*cnx_clean{{{*/
void
cnx_clean(pTHX_ imp_dbh_t * imp_dbh)
{
    if(imp_dbh->errhp != NULL)
    {
        (void)OCIHandleFree(imp_dbh->errhp, OCI_HTYPE_ERROR);
        imp_dbh->errhp = NULL;
    }
    if(imp_dbh->seshp != NULL)
    {
        (void)OCIHandleFree(imp_dbh->seshp, OCI_HTYPE_SESSION);
        imp_dbh->seshp = NULL;
    }
    if(imp_dbh->svchp != NULL)
    {
        (void)OCIHandleFree(imp_dbh->svchp, OCI_HTYPE_SVCCTX);
        imp_dbh->svchp = NULL;
    }
    if(imp_dbh->srvhp != NULL)
    {
        (void)OCIHandleFree(imp_dbh->srvhp, OCI_HTYPE_SERVER);
        imp_dbh->srvhp = NULL;
    }

#ifdef ORA_OCI_112
    if(imp_dbh->lock->refs < 0)
    {
        imp_dbh->lock->refs++;
        if(imp_dbh->lock->refs == 0)
            release_pool(aTHX_ (pool_box_t *)imp_dbh->lock);
    }
    else
    {
#endif
        imp_dbh->lock->refs--;
        if(imp_dbh->lock->refs == 0)
            release_env(aTHX_ (env_box_t *)imp_dbh->lock);
#ifdef ORA_OCI_112
    }
#endif
}/*}}}*/

void
cnx_drop_dr(pTHX_ imp_drh_t * imp_drh)
{
    llist_t * el;
#if defined(USE_ITHREADS)
    MUTEX_LOCK(&mng_lock);
#endif
    dr_instances--;
    el = llist_left(&mng_list);
    while(el != &mng_list)
    {
        box_t * base = (box_t *)el;
#ifdef ORA_OCI_112
        int is_pool = 0;
        if(base->refs < 0)
        {
            base->refs++;
            is_pool = 1;
        }
        else
#endif
        base->refs--;
        el = llist_left(el);
        if(base->refs == 0)
        {
#ifdef ORA_OCI_112
            if(is_pool)
                release_pool(aTHX_ (pool_box_t *)base);
            else
#endif
                release_env(aTHX_ (env_box_t *)base);
        }
    }
#if defined(USE_ITHREADS)
    MUTEX_UNLOCK(&mng_lock);
#endif
}

/* in vim: set foldmethod=marker: */
