DataMuseum.dk

Presents historical artifacts from the history of:

DKUUG/EUUG Conference tapes

This is an automatic "excavation" of a thematic subset of
artifacts from Datamuseum.dk's BitArchive.

See our Wiki for more about DKUUG/EUUG Conference tapes

Excavated with: AutoArchaeologist - Free & Open Source Software.


top - metrics - download
Index: T d

⟦2b91ad100⟧ TextFile

    Length: 39321 (0x9999)
    Types: TextFile
    Names: »dsa_chain.c«

Derivation

└─⟦2d1937cfd⟧ Bits:30007241 EUUGD22: P.P 5.0
    └─⟦35176feda⟧ »EurOpenD22/isode/isode-6.tar.Z« 
        └─⟦de7628f85⟧ 
            └─⟦this⟧ »isode-6.0/quipu/dsa_chain.c« 

TextFile

/* dsa_chain.c - take referral and chain if allowed */

#ifndef lint
static char *rcsid = "$Header: /f/osi/quipu/RCS/dsa_chain.c,v 7.2 89/12/19 16:20:26 mrose Exp $";
#endif

/*
 * $Header: /f/osi/quipu/RCS/dsa_chain.c,v 7.2 89/12/19 16:20:26 mrose Exp $
 *
 *
 * $Log:	dsa_chain.c,v $
 * Revision 7.2  89/12/19  16:20:26  mrose
 * sync
 * 
 * Revision 7.1  89/11/27  10:30:12  mrose
 * sync
 * 
 * Revision 7.0  89/11/23  22:17:21  mrose
 * Release 6.0
 * 
 */

/*
 *                                NOTICE
 *
 *    Acquisition, use, and distribution of this module and related
 *    materials are subject to the restrictions of a license agreement.
 *    Consult the Preface in the User's Manual for the full terms of
 *    this agreement.
 *
 */


#include "acsap.h"
#include "quipu/util.h"
#include "quipu/connection.h"

extern LLog * log_dsap;

extern int dn_print ();
extern char	* mydsaname;
extern DN	  mydsadn;
extern int	  no_dsp_chain;

struct oper_act	* task2oper();
struct di_block	* di_alloc();
struct di_block	* select_refer_dsa();
Conn    conn_alloc();
struct oper_act * oper_alloc();
struct PSAPaddr	* psap_cpy();
struct access_point *ap_cpy ();

Conn	make_conn_block(name, addr, conn_ctx)
DN			  name;
struct PSAPaddr		* addr;
char			  conn_ctx;
{
    Conn	  cn;

    /*
    * Set up a new connection block and add it to the list.
    */

    (void) dn_decode (name);
    if(dn_cmp(name, mydsadn) == 0)
    {
	LLOG(log_dsap, LLOG_FATAL, ("Trying to connect to self :-)"));
	return(NULLCONN);
    }


    if((cn = conn_alloc()) == NULLCONN)
    {
	LLOG(log_dsap, LLOG_EXCEPTIONS, ("make_conn_block - conn_alloc() out of memory"));
	return(NULLCONN);
    }
    cn->cn_state = CN_WAITING;
    cn->cn_ctx = conn_ctx;
    cn->cn_initiator = INITIATED_BY_THIS;
    make_dsa_bind_arg(&(cn->cn_init_act.ia_req));

    cn->cn_what = dn_cpy(name);
    DLOG (log_dsap,LLOG_TRACE,( "Before psap_dup: %s", paddr2str(addr,NULLNA)));
    psap_dup(&(cn->cn_addr), addr);
    DLOG (log_dsap,LLOG_TRACE,( "After psap_dup:  %s", paddr2str(&(cn->cn_addr),NULLNA)));

    return(cn);
}

int	  link_op_to_conn(on)
struct oper_act	* on;
{
    char		  conn_ctx = CN_CTX_X500_DSP;
    struct di_block	* di;
    struct di_block	**next_di;
    struct connection	* cn;
    int			  do_conn;
    struct access_point * loop_ap;
    int 		res;

    sort_dsa_list (&on->on_dsas);

    /*
    *  Use an open connection if one is available.
    */

    next_di = &(on->on_dsas);
    for(di=on->on_dsas; di!=NULL_DI_BLOCK; di=di->di_next)
    {
	for(cn=connlist; cn!=NULLCONN; cn=cn->cn_next)
	{
	    /* Must be a suitable context */
	    if(cn->cn_ctx == CN_CTX_X500_DAP)
	    {
		LLOG(log_dsap, LLOG_TRACE, ("link_op_to_conn - open conn has DAP context"));
		continue;
	    }

	    if((cn->cn_ctx == CN_CTX_X500_DSP)
		&& ((on->on_type == ON_TYPE_GET_EDB)
		    || (on->on_type == ON_TYPE_GET_DSA_INFO)
		    || (cn->cn_initiator == INITIATED_BY_THAT)))
	    {
		if (cn->cn_initiator == INITIATED_BY_THAT)
			LLOG(log_dsap, LLOG_TRACE, ("link_op_to_conn - open conn has DSP context - we must initiate it"));
		else
			LLOG(log_dsap, LLOG_TRACE, ("link_op_to_conn - open conn has DSP context - QUIPU context needed"));
		continue;
	    }

	    if((cn->cn_what != NULLDN)
	      && (dn_cmp(cn->cn_what, di->di_dn) == 0))
		break;

	}
	if(cn != NULLCONN)
	    break;

	next_di = &(di->di_next);
    }
    if(di != NULL_DI_BLOCK)
    {
	/* Got one - remove successful di_block and link op to conn */
	DLOG(log_dsap, LLOG_TRACE, ("link_op_to_conn - Found suitable open connection"));
	(*next_di) = di->di_next;
	if (di->di_state == DI_DEFERRED) {
		/* We have an open connection, but not a cache entry */
		/*		(must have used an access point in the past) */
		/* Need to be careful about freeing - do it later ! */
		di->di_oper = NULLOPER;
	} else
		di_extract(di);
	on->on_conn = cn;
	on->on_next_conn = cn->cn_operlist;
	cn->cn_operlist = on;
	return(OK);
    }

    /*
    *  Use a waiting connection if one is available.
    */
    next_di = &(on->on_dsas);
    for(di=on->on_dsas; di!=NULL_DI_BLOCK; di=di->di_next)
    {
	for(cn=connwaitlist; cn!=NULLCONN; cn=cn->cn_next)
	{
	/*
	*  Could do some clever stuff here and convert a waiting 
	*  connection to QUIPU from X500 if possible and useful.
	*  Left as an exercise for the reader.
	*/
	    /* Must be a suitable context */
	    if(cn->cn_ctx == CN_CTX_X500_DAP)
		continue;

	    if((cn->cn_ctx == CN_CTX_X500_DSP)
		&& ((on->on_type == ON_TYPE_GET_EDB)
		    || (on->on_type == ON_TYPE_GET_DSA_INFO)))
		continue;

	    if((cn->cn_what != NULLDN)
	      && (dn_cmp(cn->cn_what, di->di_dn) == 0))
		break;
	}
	if(cn != NULLCONN)
	    break;

	next_di = &(di->di_next);
    }
    if(di != NULL_DI_BLOCK)
    {
	/* Got one - remove successful di_block and link op to conn */
	LLOG(log_dsap, LLOG_TRACE, ("link_op_to_conn - Found suitable waiting connection"));
	(*next_di) = di->di_next;
	di_extract(di);
	on->on_conn = cn;
	on->on_next_conn = cn->cn_operlist;
	cn->cn_operlist = on;
	return(OK);
    }

    DLOG(log_dsap, LLOG_DEBUG, ("Neither an open nor a waiting conn suitable"));

    next_di = &(on->on_dsas);
    for(di=on->on_dsas; di!=NULL_DI_BLOCK; di=(*next_di))
    {
	if(di->di_state == DI_DEFERRED)
	{
	    DLOG(log_dsap, LLOG_TRACE, ("link_op_to_conn - deferred di_block"));
	    next_di = &(di->di_next);
	    continue;
	}

	if(di->di_state == DI_ACCESSPOINT)
	{
	    /* context problem:
		if we have not got the entry, we don't know which context it
		will accept.
		If the operation is a getedb, or getdsainfo 
			ASSUME Quipu context is OK
	    */
		
	    if((on->on_type == ON_TYPE_GET_EDB)
	          || (on->on_type == ON_TYPE_GET_DSA_INFO)) 
		conn_ctx = CN_CTX_QUIPU_DSP;
	    else {
		conn_ctx = CN_CTX_X500_DSP;
	    }

	    DLOG(log_dsap, LLOG_TRACE, ("link_op_to_conn - make conn block from access point"));

	    /* There *should* only be one access point - 
	     * but QUIPU may give a choice try find one that will work...
             * This is *wrong* if it is a non specific subordinate reference.
             */
	    if (di->di_reftype == RT_NONSPECIFICSUBORDINATE)
		    LLOG(log_dsap,LLOG_EXCEPTIONS,("Should try each access point - not just one !!!"));

	    for (loop_ap=di->di_accesspoints; loop_ap != NULLACCESSPOINT; loop_ap=loop_ap->ap_next)
		    if((cn = make_conn_block(loop_ap->ap_name, loop_ap->ap_address, conn_ctx)) != NULLCONN)
			break;

	    if (loop_ap == NULLACCESSPOINT) {
		DLOG(log_dsap, LLOG_DEBUG, ("link_op_to_conn - make_conn_block failed 1"));
		(*next_di) = di->di_next;
		di_extract(di);
		continue;
	    }
	}

	if(di->di_state == DI_COMPLETE)
	{
	    /*
	    *  Open a quipu context connection if possible: this is so if
	    *  the entry for the dsa in question has object class quipuDSA.
	    */
	    if((res = quipu_ctx_supported(di->di_entry)) != 2)
	    {
		if((on->on_type == ON_TYPE_GET_EDB)
	          || (on->on_type == ON_TYPE_GET_DSA_INFO)
		  || (res == -1)) /* DAP only !!! */
		{
		    /* Ditch this di_block and carry on looking */
		    DLOG(log_dsap, LLOG_DEBUG, ("link_op_to_conn - avoiding non-quipu context for GetEDB"));
		    (*next_di) = di->di_next;
		    di_extract(di);
		    continue;
		}
		else
		{
		    DLOG(log_dsap, LLOG_DEBUG, ("link_op_to_conn - linking to a connection without a quipu context"));
		    conn_ctx = CN_CTX_X500_DSP;
		}
	    }
	    else
	    {
		DLOG(log_dsap, LLOG_DEBUG, ("link_op_to_conn - linking to a connection with a quipu context"));
		conn_ctx = CN_CTX_QUIPU_DSP;
	    }


	    DLOG(log_dsap, LLOG_TRACE, ("link_op_to_conn - make conn block from entry"));

            if((cn = make_conn_block(di->di_dn, di->di_entry->e_dsainfo->dsa_addr, conn_ctx)) == NULLCONN)
	    {
		DLOG(log_dsap, LLOG_DEBUG, ("link_op_to_conn - make_conn_block failed 2"));
		(*next_di) = di->di_next;
		di_extract(di);
		continue;
	    }
	}

	/*
	*  Decide whether to request connection or place it
	*  on the list of waiting connections.
	*/
	switch(on->on_type)
	{
	case ON_TYPE_GET_DSA_INFO:
	    do_conn = (conns_used < MAX_CONNS);
	    break;
	case ON_TYPE_GET_EDB:
	    do_conn = (conns_used < (MAX_CONNS - CONNS_RESERVED_DI - CONNS_RESERVED_X500));
	    break;
	default:
	    do_conn = (conns_used < (MAX_CONNS - CONNS_RESERVED_DI));
	    break;
	}

	if(do_conn)
	{
	    DLOG(log_dsap, LLOG_TRACE, ("link_op_to_conn - about to request connection"));
	    if(conn_request(cn) != OK)
	    {
		DLOG(log_dsap, LLOG_DEBUG, ("link_op_to_conn - conn_request failed"));
		(*next_di) = di->di_next;
		di_extract(di);
		continue;
	    }
	    DLOG(log_dsap, LLOG_DEBUG, ("link_op_to_conn - conn_request OK"));

	    cn->cn_next = connlist;
	    connlist = cn;
	    conns_used++;
	}
	else
	{
	    DLOG(log_dsap, LLOG_NOTICE, ("Waiting for a free connection slot"));
	    cn->cn_next = connwaitlist;
	    connwaitlist = cn;
	}

	(*next_di) = di->di_next;
	di_extract(di);
	on->on_conn = cn;
	on->on_next_conn = cn->cn_operlist;
	cn->cn_operlist = on;
	return(OK);
    }

    /*
    *  If we get this far it means that we are waiting for a dsa info
    *  operation to complete, or there are no di_blocks left to try.
    *  Callers of link_op_to_conn must check on_dsas to discover which it is.
    */
    DLOG(log_dsap, LLOG_NOTICE, ("link_op_to_conn: returning NOTOK"));
    return(NOTOK);
}

int	  oper_chain(on)
struct oper_act		* on;
{
    if(link_op_to_conn(on) == OK)
    {
	if(on->on_conn == NULLCONN)
	{
	    DLOG(log_dsap, LLOG_DEBUG, ("oper_chain - link_op_to_conn: OK but no conn"));
	}
	else
	{
	    DLOG(log_dsap, LLOG_DEBUG, ("oper_chain - link_op_to_conn: OK got conn"));

	    if(on->on_conn->cn_state == CN_OPEN)
	    {
		DLOG(log_dsap, LLOG_DEBUG, ("oper_chain - link_op_to_conn: OK got open conn"));
		if(oper_send_invoke(on) != OK)
		{
		    LLOG(log_dsap, LLOG_EXCEPTIONS, ("oper_chain - oper_send failed"));
		    /* Have another go? */
		    return(oper_chain(on));
		}
		else
		{ 
		    DLOG(log_dsap, LLOG_DEBUG, ("oper_chain - oper_send succeeded"));
		}
	    }
	}

	on->on_state = ON_CHAINED;
    }
    else
    {
	/*
	*  If on->on_dsas is empty then chaining has failed
	*  otherwise the op is deferred.
	*/
	if(on->on_dsas == NULL_DI_BLOCK)
	{
	    return(NOTOK);
	}
	
	on->on_state = ON_DEFERRED;
    }

    return(OK);
}

int	  task_chain(tk, di)
register       struct task_act     * tk;
struct di_block	* di;
{
    struct oper_act	* on;
    struct DSError	* err = &(tk->tk_resp.resp_err);
    struct di_block	* di_tmp;
    char		refer_ok = TRUE;

#ifdef DEBUG
    DLOG(log_dsap, LLOG_DEBUG, ("task_chain called with:"));
    di_list_log(di);
#endif

/* NB At some point this routine must assign the di_block list to
* either the task (if it is intended to geneate a referral) or to
* an operation hanging off that task if it is intended to chain the
* task. This is fine when there are no deferred di_blocks, but when
* there are then the information they will eventually contain is
* needed to make a full decision on whether to chain or refer.
* This needs a lot of thought to get right, for now the chain/refer
* decision is made once and for all on the basis of the information
* available now. Any information not available is assumed to force a
* referral (the safe option  -  until network connectivity is considered)!
* THis may introduce the unwelcome effect that a first request to a
* DSA may produce a referral where subsequent requests do not - so much
* for consistency but it won't happen that often if DSA info is cached
* sensibly.
*/

    /*
    *  Generate the referral which the DSA will pass back if
    *  chaining is disallowed or oper_chain fails for all
    *  DSAs listed.
    */

    sort_dsa_list (&di);

    if ((di_tmp = select_refer_dsa (di,tk)) == NULL_DI_BLOCK) {
	/* The remote END is probably unable to follow the referral - chain if allowed */
	refer_ok = FALSE;
	for(di_tmp=di; di_tmp!=NULL_DI_BLOCK; di_tmp=di_tmp->di_next)
    	{
		if(di_tmp->di_state == DI_DEFERRED)
			continue;

#ifdef DEBUG
		DLOG(log_dsap, LLOG_DEBUG, ("About to call di2cref with:"));
		di_log(di_tmp);
#endif
		if(di2cref(di_tmp, err, tk->tk_conn->cn_ctx) == OK)
		    break;
	}
    } else if (di2cref(di_tmp, err, tk->tk_conn->cn_ctx) != OK)
		di_tmp = NULL_DI_BLOCK;	/* waiting... */

    if(di_tmp == NULL_DI_BLOCK)
    {
	/*
	*  Want to generate a referral - but all di_blocks (if any)
	*  are deferred. Would we be lying too much if we said the
	*  DSA was "busy" at this point???
	*/
	ds_error_free (err);
	err->dse_type = DSE_SERVICEERROR;
	err->ERR_SERVICE.DSE_sv_problem = DSE_SV_BUSY;
	di_desist(di);
	return(NOTOK);
    }

    /*
    *  If it would be inappropriate to chain this operation, then
    *  generate a referral from the di_block list.
    */
    if(chain_ok(tk,refer_ok) == FALSE)
    {
	DLOG(log_dsap, LLOG_DEBUG, ("Referring!"));
	di_desist(di);
	return(NOTOK);
    }

    DLOG(log_dsap, LLOG_DEBUG, ("Chaining!"));
    /* Chain. Generate the new operation to send */
    if((on = task2oper(tk)) == NULLOPER)
    {
	DLOG(log_dsap, LLOG_DEBUG, ("Why did task2oper fail??"));
	ds_error_free (err);
	err->dse_type = DSE_SERVICEERROR;
	err->ERR_SERVICE.DSE_sv_problem = DSE_SV_UNAVAILABLE;
	di_desist(di);
	return(NOTOK);
    }

    on->on_next_task = tk->tk_operlist;
    tk->tk_operlist = on;
    on->on_task = tk;

    /* Hand control of di_blocks to the operation */
    on->on_dsas = di;
    for(di_tmp = di; di_tmp != NULL_DI_BLOCK; di_tmp=di_tmp->di_next)
    {
	di_tmp->di_type = DI_OPERATION;
	di_tmp->di_oper = on;
    }

    if(oper_chain(on) != OK)
    {
	oper_task_extract(on);
	oper_free(on);
	return(NOTOK);
    }

    return(OK);
}

oper_rechain(on)
struct oper_act * on;
{
    struct DSE_referral         * ref = &(on->on_resp.resp_err.ERR_REFERRAL);
    struct continuation_ref     * cref;
    register struct chain_arg	* cha = &(on->on_req.dca_charg);
    struct trace_info		* ti;
    struct di_block * ap2di();

    DLOG(log_dsap, LLOG_TRACE, ("Rechain an operation ..."));
    cref = ref->DSE_ref_candidates;
    
    if(cref == NULLCONTINUATIONREF)
    {
	LLOG(log_dsap, LLOG_FATAL, ("No continuation reference to rechain"));
	on->on_resp.resp_type = RESP_TYPE_REJ;
	return(NOTOK);
    }

    (void) dn_decode(cref->cr_name);
    cha->cha_target = dn_cpy(cref->cr_name);
    cha->cha_progress = cref->cr_progress;
    cha->cha_aliasderef = ((cha->cha_aliasedrdns = cref->cr_aliasedRDNs) != CR_NOALIASEDRDNS);
    cha->cha_returnrefs = FALSE;
    cha->cha_domaininfo = NULLPE;
    if((cha->cha_reftype = cref->cr_reftype) == RT_UNDEFINED)
	cha->cha_reftype = RT_SUPERIOR;

    DLOG(log_dsap, LLOG_DEBUG, ("oper_rechain - Setting trace info"));
    ti = (struct trace_info *) malloc(sizeof(struct trace_info));
    ti->ti_dsa = dn_cpy(on->on_conn->cn_what); 
    ti->ti_target = dn_cpy(cref->cr_name);
    ti->ti_progress = cref->cr_progress;
    ti->ti_next = cha->cha_trace;
    cha->cha_trace = ti;

    if(ti_is_elem(ti,ti->ti_next))
	{
	    DLOG (log_dsap,LLOG_NOTICE,("Loop found in oper_rechain()"));
	    ds_error_free (&on->on_resp.resp_err);
	    on->on_resp.resp_err.dse_type = DSE_SERVICEERROR;
	    on->on_resp.resp_err.ERR_SERVICE.DSE_sv_problem = DSE_SV_LOOPDETECT;
	    return(NOTOK);
	}

    oper_conn_extract(on);

    /*
    *  Problem - having converted to di_blocks it is harder to handle referrals
    *  Set up a single di_block with the address in the parent field ??
    */
    di_desist(on->on_dsas);
    if (cref->cr_reftype != RT_NONSPECIFICSUBORDINATE)
	    on->on_dsas = ap2di (cref->cr_accesspoints,cref->cr_name,FALSE,DI_OPERATION,on,cref->cr_reftype);
    else {
	    on->on_dsas = di_alloc();
	    (void) dn_decode (cref->cr_name);
	    on->on_dsas->di_target = dn_cpy(cref->cr_name);
	    (void) dn_decode (cref->cr_accesspoints->ap_name);
	    on->on_dsas->di_dn = dn_cpy(cref->cr_accesspoints->ap_name);
	    DLOG(log_dsap, LLOG_DEBUG, ("oper_rechain allocates di_block with dn[%x]", on->on_dsas->di_dn));
	    on->on_dsas->di_type = DI_OPERATION;
	    on->on_dsas->di_reftype = RT_NONSPECIFICSUBORDINATE;
	    on->on_dsas->di_oper = on;
	    on->on_dsas->di_state = DI_ACCESSPOINT;
	    on->on_dsas->di_accesspoints = ap_cpy(cref->cr_accesspoints);
	    on->on_dsas->di_next = NULL_DI_BLOCK;
    }

    sort_dsa_list (&on->on_dsas);	/* might be able to turn DI_ACCESS into DI_COMPLETE */
    return(oper_chain(on));
}

struct oper_act	* task2oper(tk)
struct task_act * tk;
{
    register struct chain_arg	* cha = &(tk->tk_req.dca_charg);
    struct continuation_ref	* cref = tk->tk_resp.resp_err.ERR_REFERRAL.DSE_ref_candidates;
    struct trace_info		* ti;
    struct oper_act		* on;

    DLOG(log_dsap, LLOG_TRACE, ("Chain a task ..."));
    
    if((on = oper_alloc()) == NULLOPER)
	return(NULLOPER);

    on->on_type = ON_TYPE_X500;

    cha->cha_target = NULLDN;
    if(cref->cr_name != NULLDN)
    {
	    cha->cha_target = dn_cpy(cref->cr_name);
    }
    cha->cha_progress = cref->cr_progress;
    cha->cha_aliasderef = ((cha->cha_aliasedrdns = cref->cr_aliasedRDNs) != CR_NOALIASEDRDNS);
    cha->cha_returnrefs = FALSE;
    cha->cha_domaininfo = NULLPE;
    if((cha->cha_reftype = cref->cr_reftype) == RT_UNDEFINED)
	cha->cha_reftype = RT_SUPERIOR;

    DLOG(log_dsap, LLOG_DEBUG, ("Checking history of op"));
    if(tk->tk_conn->cn_ctx == CN_CTX_X500_DAP)
    {
	DLOG(log_dsap, LLOG_DEBUG, ("... user originated ..."));
	cha->cha_originator = dn_cpy(tk->tk_conn->cn_who);
	cha->cha_trace = NULLTRACEINFO;
    }

    if(tk->tk_timed == FALSE)
    {
	cha->cha_timelimit = NULLCP;
    }
    else
    {
#ifdef CHAIN_ARGS_TIMEOUT
	struct UTCtime	ut;
	tm2ut(gmtime(&(tk->tk_timeout)), &(ut));
	cha->cha_timelimit = strdup(utct2str(&ut));
#else
	cha->cha_timelimit = NULLCP;
#endif
    }

    DLOG(log_dsap, LLOG_DEBUG, ("Setting trace info"));
    ti = (struct trace_info *) malloc(sizeof(struct trace_info));
    ti->ti_dsa = dn_cpy(mydsadn);
    ti->ti_target = dn_cpy(cref->cr_name);
    ti->ti_progress = cref->cr_progress;
    ti->ti_next = cha->cha_trace;
    cha->cha_trace = ti;

    on->on_arg = &(tk->tk_req);

    return(on);
}

int     chain_ok(tk,refer_ok)
struct task_act	* tk;
char refer_ok;
{
    /* if refer_ok is FALSE - we MUST chain unless prevented, otherwise operation will fail */

    DLOG (log_dsap,LLOG_TRACE,( "chain_ok: Checking if chaining is ok"));

    if ( ! refer_ok) {
	DLOG (log_dsap,LLOG_DEBUG,( "We MUST chain"));	
	
	if ((tk->tk_conn->cn_ctx != CN_CTX_X500_DAP) && no_dsp_chain)
	{
		DLOG (log_dsap,LLOG_DEBUG,( "Not chaining because of NO_DSP_CHAIN"));
		return(FALSE);
	}
	if(tk->tk_ca->ca_servicecontrol.svc_options & SVC_OPT_CHAININGPROHIBIT)
	{
		DLOG (log_dsap,LLOG_DEBUG,( "But prohibited"));
		return(FALSE);
	}

	if(tk->tk_ca->ca_servicecontrol.svc_options & SVC_OPT_LOCALSCOPE)
	{
		DLOG (log_dsap,LLOG_DEBUG,( "But out of scope"));
		return(FALSE); 
	}
	
    	DLOG (log_dsap,LLOG_DEBUG,( "Forced chain OK!"));
	return TRUE;
    }

    if (tk->tk_conn->cn_ctx != CN_CTX_X500_DAP) {
	if (no_dsp_chain)
	{
		DLOG (log_dsap,LLOG_DEBUG,( "Not chaining because of NO_DSP_CHAIN (2)"));
		return(FALSE);
	}

	if(! (tk->tk_ca->ca_servicecontrol.svc_options & SVC_OPT_PREFERCHAIN))
	{
		DLOG (log_dsap,LLOG_DEBUG,( "Not chaining because of preference"));
		return(FALSE);
	}
    }

    if(tk->tk_ca->ca_servicecontrol.svc_options & SVC_OPT_CHAININGPROHIBIT)
    {
	DLOG (log_dsap,LLOG_DEBUG,( "Not chaining because of prohibition"));
	return(FALSE);
    }

    if(tk->tk_ca->ca_servicecontrol.svc_options & SVC_OPT_LOCALSCOPE)
    {
	DLOG (log_dsap,LLOG_DEBUG,( "Not chaining because of scope"));
	return(FALSE); 
    }

    switch (tk->tk_req.dca_dsarg.arg_type) {
	case OP_ADDENTRY:
	case OP_REMOVEENTRY:
	case OP_MODIFYRDN:
	case OP_MODIFYENTRY:
		/* QUIPU DSAs will only allow modification over DAP */
		if (!(tk->tk_ca->ca_servicecontrol.svc_options & SVC_OPT_PREFERCHAIN)) {
			DLOG (log_dsap,LLOG_DEBUG,( "Not chaining because of authentication"));
			return(FALSE); 
		}
	default:
		break;
    }

    DLOG (log_dsap,LLOG_DEBUG,( "Chain OK!"));
    return(TRUE);
}

task_result_wakeup(on)
struct oper_act	* on;
{
    struct task_act	* tk;

    DLOG(log_dsap, LLOG_TRACE, ("task_result_wakeup"));

    if((tk = on->on_task) == NULLTASK)
    {
	LLOG(log_dsap, LLOG_EXCEPTIONS, ("Oper can't wake up (result)extracted task"));
	oper_extract(on);
    }
    else
    {
	/*
	*  Were waiting for a remote result and here it is.
	*  Attempt to tidy up and send result.
	*/
	tk->tk_result = &(on->on_resp.resp_res);
	tk->tk_resp.resp_type = RESP_TYPE_RET;
	on->on_resp.ret_type = RET_TYPE_RES;
	
	dsp_cache (&(tk->tk_req.dca_dsarg),&(tk->tk_result->dcr_dsres),tk->tk_conn->cn_ctx, tk->tk_conn->cn_who);

	task_conn_extract(tk);
	task_result(tk);
	oper_extract(on);
	task_extract(tk);
    }
}

task_error_wakeup(on)
struct oper_act	* on;
{
    struct task_act	* tk;

    DLOG(log_dsap, LLOG_TRACE, ("task_error_wakeup"));

    if((tk = on->on_task) == NULLTASK)
    {
	LLOG(log_dsap, LLOG_EXCEPTIONS, ("Oper can't wake up (error) extracted task"));
	oper_extract(on);
    }
    else
    {
	/*
	*  Were waiting for a remote result and got a remote error.
	*  If it is a referral, then rechain the operation if appropriate
	*  otherwise return the error.
	*/
	if ( !  ((on->on_resp.resp_err.dse_type == DSE_SECURITYERROR)
	  	&& (on->on_resp.resp_err.ERR_SECURITY.DSE_sc_problem == DSE_SC_AUTHENTICATION))) {
		/* If is not an authenticaton error, swap errors */
		ds_error_free(&(tk->tk_resp.resp_err));
		tk->tk_error = &(on->on_resp.resp_err);
	}
	tk->tk_resp.resp_type = RESP_TYPE_RET;

	if((on->on_resp.resp_err.dse_type == DSE_DSAREFERRAL)
  	|| (on->on_resp.resp_err.dse_type == DSE_REFERRAL))
	{
	    DLOG(log_dsap, LLOG_DEBUG, ("Try rechaining"));
	    if(oper_rechain(on) == OK)
	    {
		DLOG(log_dsap, LLOG_DEBUG, ("Succeeded rechaining"));
		return;
	    }
	    DLOG(log_dsap, LLOG_DEBUG, ("Failed rechaining"));
	}

	task_conn_extract(tk);
	task_error(tk);
	oper_extract(on);
	task_extract(tk);
    }
}

task_fail_wakeup(on)
struct oper_act	* on;
{
    struct task_act	* tk;
    struct DSError	* err;

    DLOG(log_dsap, LLOG_TRACE, ("task_fail_wakeup"));

    if((tk = on->on_task) == NULLTASK)
    {
	if (on->on_state != ON_ABANDONED)
		LLOG(log_dsap, LLOG_EXCEPTIONS, ("task_fail_wakeup: no task"));
	oper_extract(on);
	return;
    }

    /*
    *  Were waiting for a remote result and got a remote failure.
    *  If it is a referral, then rechain the operation if appropriate
    *  otherwise return the error.
    */
    /*
    *  If the task does not have a suitable referral error set up
    *  then return serviceError invalid reference.
    */
    err = &(tk->tk_resp.resp_err);
    if((err->dse_type != DSE_REFERRAL) && (err->dse_type != DSE_DSAREFERRAL))
    {
	err->dse_type = DSE_SERVICEERROR;
	err->ERR_SERVICE.DSE_sv_problem = DSE_SV_UNAVAILABLE;
    }
    task_conn_extract(tk);
    task_error(tk);
    oper_conn_extract(on);
    oper_task_extract(on);
    oper_extract(on);
    task_extract(tk);
}

task_dsa_info_wakeup(di)
struct di_block	* di;
{
    struct task_act	* tk = di->di_task;


    DLOG(log_dsap, LLOG_TRACE, ("task_dsa_info_wakeup"));

    /*
    * Were waiting for a reference to return.
    * Check if the reference now returned is acceptable.
    * If it is return a referral and unwrap everything,
    * otherwise try another di_block for the reference.
    */
    sort_dsa_list (&di);

    if(di2cref(di, &(tk->tk_resp.resp_err), tk->tk_conn->cn_ctx) != OK)
    {
	LLOG(log_dsap, LLOG_EXCEPTIONS, ("task_dsa_info_wakeup - reference not acceptable"));
	/* Remove di_block which generated unwanted referral wait */
	if(tk->tk_dsas == NULL_DI_BLOCK)
	{
	    /* No more dsas from which to request info to form referral */
	    tk->tk_resp.resp_err.dse_type = DSE_SERVICEERROR;
	    tk->tk_resp.resp_err.ERR_SERVICE.DSE_sv_problem = DSE_SV_INVALIDREFERENCE;
	    task_conn_extract(tk);
	    task_error(tk);
	    task_extract(tk);
	    return;
	}
	return;
    }

    task_conn_extract(tk);
    task_error(tk);
    task_extract(tk);
}

static struct access_point * di2ap (di)
struct di_block * di;
{
struct access_point *ap;

    switch(di->di_state)
    {
    case DI_ACCESSPOINT:
	return (ap_cpy(di->di_accesspoints));
    case DI_COMPLETE:
	if(di->di_entry == NULLENTRY)
	{
	    LLOG(log_dsap, LLOG_EXCEPTIONS, ("di2ap - di_entry NULL"));
	    return NULLACCESSPOINT;
	}
	if(di->di_entry->e_dsainfo == NULL)
	{
	    LLOG(log_dsap, LLOG_EXCEPTIONS, ("di2ap - e_dsainfo NULL"));
	    return NULLACCESSPOINT;
	}
	if(di->di_entry->e_dsainfo->dsa_addr == NULLPA)
	{
	    LLOG(log_dsap, LLOG_EXCEPTIONS, ("di2ap - dsa_addr NULL"));
	    return NULLACCESSPOINT;
	}
	ap = (struct access_point *) calloc(1, sizeof(struct access_point));
	ap->ap_name = dn_cpy(di->di_dn);
	ap->ap_address = psap_cpy(di->di_entry->e_dsainfo->dsa_addr);
	return (ap);
    default:
	return NULLACCESSPOINT;
    }
	
}

int	di2cref(di, err, ctx)
struct di_block	* di;
struct DSError	* err;
char ctx;
{
    struct continuation_ref     * cref;
    struct di_block * loop;
    struct access_point *ap_append(), *di2ap(), *ap;

#ifdef DEBUG
    DLOG(log_dsap, LLOG_TRACE, ("di2cref"));
    di_log(di);
#endif

    switch(di->di_state)
    {
    case DI_ACCESSPOINT:
        DLOG(log_dsap, LLOG_TRACE, ("di2cref - generating referrral from di_accesspoints"));

	/* Should check context */
	err->dse_type = DSE_REFERRAL;
	err->ERR_REFERRAL.DSE_ref_prefix = NULLDN;

        cref = err->ERR_REFERRAL.DSE_ref_candidates = (struct continuation_ref *) calloc(1, sizeof(struct continuation_ref));
	cref->cr_accesspoints = ap_cpy(di->di_accesspoints);
	cref->cr_name = dn_cpy(di->di_target);
	if((cref->cr_rdn_resolved = di->di_rdn_resolved) <= 0)
	{
	    cref->cr_progress.op_resolution_phase = OP_PHASE_NOTSTARTED;
	    cref->cr_progress.op_nextrdntoberesolved = 0;
	}
	else
	{
	    cref->cr_progress.op_resolution_phase = OP_PHASE_PROCEEDING;
	    cref->cr_progress.op_nextrdntoberesolved = di->di_rdn_resolved;
	}
	cref->cr_aliasedRDNs = di->di_aliasedRDNs;
	cref->cr_reftype = di->di_reftype;
	break;
    case DI_COMPLETE:
        DLOG(log_dsap, LLOG_TRACE, ("di2cref - generating referrral from di_entry"));

	/* Should check context */
	err->dse_type = DSE_REFERRAL;
	err->ERR_REFERRAL.DSE_ref_prefix = NULLDN;

        cref = err->ERR_REFERRAL.DSE_ref_candidates = (struct continuation_ref *) calloc(1, sizeof(struct continuation_ref));
	if ((cref->cr_accesspoints = di2ap (di)) == NULLACCESSPOINT)
		return NOTOK;
	cref->cr_name = dn_cpy(di->di_target);
	if((cref->cr_rdn_resolved = di->di_rdn_resolved) <= 0)
	{
	    cref->cr_progress.op_resolution_phase = OP_PHASE_NOTSTARTED;
	    cref->cr_progress.op_nextrdntoberesolved = 0;
	}
	else
	{
	    cref->cr_progress.op_resolution_phase = OP_PHASE_PROCEEDING;
	    cref->cr_progress.op_nextrdntoberesolved = di->di_rdn_resolved;
	}
	cref->cr_aliasedRDNs = di->di_aliasedRDNs;
	cref->cr_reftype = di->di_reftype;
	break;
    case DI_DEFERRED:
	LLOG(log_dsap, LLOG_NOTICE, ("di2cref - Trying to turn deferred di_block into continuation reference!"));
	return(NOTOK);
    default:
        LLOG(log_dsap, LLOG_EXCEPTIONS, ("di2cref - invalid di_state %d",di->di_state));
        return(NOTOK);
    }

    if (ctx == CN_CTX_QUIPU_DSP) {
	/* Make QSSR */
	/* append AP's from remaining di_blocks */
	LLOG (log_dsap, LLOG_TRACE, ("Making a QSSR"));
	for (loop=di->di_next; loop!=NULL_DI_BLOCK; loop=loop->di_next) {
		if (( ap = di2ap(loop)) == NULLACCESSPOINT)
			return OK;	/* Have finished - return OK */
		cref->cr_accesspoints = ap_append (cref->cr_accesspoints,ap);
	}
    }

    return OK;
}

oper_fail_wakeup(on)
struct oper_act	* on;
{
    DLOG(log_dsap, LLOG_TRACE, ("oper_fail_wakeup()"));
    /*
    *  Attempt to perform operation remotely has failed.
    *  Check the type of operation and take appropriate action.
    */

    switch(on->on_type)
    {
    case ON_TYPE_X500:
	task_fail_wakeup(on);
	break;
    case ON_TYPE_SUBTASK:
	subtask_fail_wakeup(on);
	break;
    case ON_TYPE_BIND_COMPARE:
	bind_compare_fail_wakeup(on);
	break;
    case ON_TYPE_GET_DSA_INFO:
	dsa_info_fail_wakeup(on);
	break;
    case ON_TYPE_NAMESERVICE:
	nameservice_fail_wakeup(on);
	break;
    case ON_TYPE_GET_EDB:
	get_edb_fail_wakeup(on);
	break;
    default:
	LLOG(log_dsap, LLOG_EXCEPTIONS, ("oper_fail_wakeup - op has invalid type"));
	break;
    }
}


subtask_chain(tk)
struct task_act * tk;
{
    struct ds_search_task	*refer;
    struct ds_search_task	*nref;
    struct ds_search_task	* trail = NULL_ST;
    struct ds_search_task 	* st_done();
    struct oper_act		* on;
    struct di_block     	* di;
    struct di_block     	* di_tmp;
    register struct chain_arg	* tkcha = &(tk->tk_req.dca_charg);
    register struct chain_arg	* oncha;
    struct trace_info		* ti;
    struct DSError		err;

    if(tk->refer_st == NULL_ST)
	return;

    DLOG(log_dsap, LLOG_TRACE, ("Chain search subtasks ..."));

    for(refer = tk->refer_st; refer != NULL_ST; refer = nref)
    {
	nref = refer->st_next;
	if((di = refer->st_di) == NULL_DI_BLOCK)
	{
	    LLOG(log_dsap, LLOG_EXCEPTIONS, ("search referred without di_block list"));
	    continue;
	}

	sort_dsa_list (&di);

	err.ERR_REFERRAL.DSE_ref_candidates = NULLCONTINUATIONREF;
	if ((di_tmp = select_refer_dsa (di,tk)) == NULL_DI_BLOCK) {
		/* The remote END is probably unable to follow the referral - chain if allowed */
		for(di_tmp=di; di_tmp!=NULL_DI_BLOCK; di_tmp=di_tmp->di_next)
	    	{
			if(di_tmp->di_state == DI_DEFERRED)
				continue;

#ifdef DEBUG
			DLOG(log_dsap, LLOG_DEBUG, ("About to call di2cref with:"));
			di_log(di_tmp);
#endif
			if(di2cref(di_tmp, &err, tk->tk_conn->cn_ctx) == OK)
			    break;
		}
	} else 
		(void) di2cref(di_tmp, &err, tk->tk_conn->cn_ctx);

	on = oper_alloc();
	on->on_type = ON_TYPE_SUBTASK;
	on->on_dsas = di;
	for(di_tmp=di; di_tmp!=NULL_DI_BLOCK; di_tmp=di_tmp->di_next)
	{
	    di_tmp->di_type = DI_OPERATION;
	    di_tmp->di_oper = on;
	}
	on->on_subtask = refer;
	on->on_task = tk;
	on->on_next_task = tk->tk_operlist;
	tk->tk_operlist = on;

	oncha = &(on->on_req.dca_charg);

	if(refer->st_alias == NULLDN)
	{
	    oncha->cha_target = dn_cpy(refer->st_baseobject);
	}
	else
	{
	    oncha->cha_target = dn_cpy(refer->st_alias);
	}

	if(di->di_rdn_resolved <= 0)
	{
	    oncha->cha_progress.op_resolution_phase = OP_PHASE_NOTSTARTED;
	    oncha->cha_progress.op_nextrdntoberesolved = 0;
	}
	else
	{
	    oncha->cha_progress.op_resolution_phase = OP_PHASE_PROCEEDING;
	    oncha->cha_progress.op_nextrdntoberesolved = di->di_rdn_resolved;
	}

	oncha->cha_aliasderef = ((oncha->cha_aliasedrdns = di->di_aliasedRDNs) != CR_NOALIASEDRDNS);
	if((oncha->cha_reftype = di->di_reftype) == RT_UNDEFINED)
	    oncha->cha_reftype = RT_SUPERIOR;
	oncha->cha_returnrefs = FALSE;
	oncha->cha_domaininfo = NULLPE;

	if(tk->tk_timed == FALSE)
	{
	    oncha->cha_timelimit = NULLCP;
	}
	else
	{
#ifdef CHAIN_ARGS_TIMEOUT
	    struct UTCtime	ut;
	    tm2ut(gmtime(&(tk->tk_timeout)), &(ut));
	    oncha->cha_timelimit = strdup(utct2str(&ut));
#else
	    oncha->cha_timelimit = NULLCP;
#endif
	}

	DLOG(log_dsap, LLOG_DEBUG, ("Checking history of op"));
	if(tk->tk_conn->cn_ctx == CN_CTX_X500_DAP)
	{
	    DLOG(log_dsap, LLOG_DEBUG, ("... user originated ..."));
	    oncha->cha_originator = dn_cpy(tk->tk_conn->cn_who);
	    oncha->cha_trace = NULLTRACEINFO;
	}
	else
	{
	    oncha->cha_originator = dn_cpy(tk->tk_req.dca_charg.cha_originator);
	    oncha->cha_trace = ti_cpy(tkcha->cha_trace);
	}

	DLOG(log_dsap, LLOG_DEBUG, ("Setting trace info"));
	ti = (struct trace_info *) malloc(sizeof(struct trace_info));
	ti->ti_dsa = dn_cpy(mydsadn);
	ti->ti_target = dn_cpy(di->di_target);

	if(di->di_rdn_resolved <= 0)
	{
	    ti->ti_progress.op_resolution_phase = OP_PHASE_NOTSTARTED;
	    ti->ti_progress.op_nextrdntoberesolved = 0;
	}
	else
	{
	    ti->ti_progress.op_resolution_phase = OP_PHASE_PROCEEDING;
	    ti->ti_progress.op_nextrdntoberesolved = di->di_rdn_resolved;
	}

	ti->ti_next = oncha->cha_trace;
	oncha->cha_trace = ti;

	on->on_req.dca_dsarg = tk->tk_req.dca_dsarg;
	on->on_req.dca_dsarg.arg_sr.sra_subset = refer->st_subset;
	on->on_req.dca_dsarg.arg_sr.sra_baseobject = dn_cpy (oncha->cha_target);
	on->on_arg = &(on->on_req);

	DLOG(log_dsap, LLOG_DEBUG, ("Generating search subtask OP"));
	if( (tk->tk_ca->ca_servicecontrol.svc_options & SVC_OPT_CHAININGPROHIBIT) 
		|| (oper_chain(on) != OK))
	{
	   add_cref2poq (&tk->tk_resp.resp_res.dcr_dsres.res_sr, err.ERR_REFERRAL.DSE_ref_candidates);
	   oper_task_extract(on);
	   oper_free(on);
		
	   if (trail == NULL_ST)
		tk->refer_st = st_done(&refer);
	   else
		trail->st_next = st_done (&refer);
	} else {
		refer->st_cr = err.ERR_REFERRAL.DSE_ref_candidates;
		trail = refer;
	}
    }
    if (trail != NULL_ST)
	trail->st_next = tk->referred_st;
    tk->referred_st = tk->refer_st;
    tk->refer_st = NULL_ST;

    if((tk->referred_st == NULL_ST) && (tk->tk_state == TK_PASSIVE) && (tk->tk_operlist == NULLOPER))
	{
	    task_conn_extract(tk);
	    task_result(tk);
	    task_extract(tk);
	}

}

subtask_result_wakeup(on)
struct oper_act	* on;
{
    struct task_act	* tk;
    struct ds_search_task	**next_st;
    struct ds_search_task	* st;

    DLOG(log_dsap, LLOG_TRACE, ("subtask_result_wakeup"));

    if((tk = on->on_task) == NULLTASK)
    {
	LLOG(log_dsap, LLOG_EXCEPTIONS, ("Oper can't wake up (result)extracted task"));
	oper_extract(on);
    }
    else
    {
	/*
	*  Were waiting for a remote subtask result and here it is.
	*/
	next_st = &(tk->referred_st);
	for(st=tk->referred_st; st!=NULL_ST; st=(*next_st))
	{
	    if(st == on->on_subtask)
		break;

	    next_st = &(st->st_next);
	}
	if(st == NULL_ST)
	{
	    LLOG(log_dsap, LLOG_EXCEPTIONS, ("subtask_result_wakeup - subtask lost from referred list"));
	}
	else
	{
	    /*
	    *  Correlate uncorrelated search results from oper,
	    *  then merge with correlated search results of task.
	    */

	    struct ds_search_result * tk_sr = &(tk->tk_resp.resp_res.dcr_dsres.res_sr);
	    struct ds_search_result * op_sr = &(on->on_resp.resp_res.dcr_dsres.res_sr);

	    DLOG(log_dsap, LLOG_DEBUG, ("Collating a search result"));

	    st_comp_free (st);
	    (*next_st) = st->st_next;

	    correlate_search_results(op_sr);
	    if(tk_sr->srr_next == NULLSRR)
	    {
		DLOG(log_dsap, LLOG_DEBUG, ("Search result unallocated!"));
		tk_sr->srr_next = (struct ds_search_result *) calloc(1, sizeof(struct ds_search_result));
		tk_sr->srr_next->srr_correlated = TRUE;
		tk_sr->srr_next->srr_un.srr_unit = (struct ds_search_unit *) calloc(1, sizeof(struct ds_search_unit));
		tk_sr->srr_next->CSR_limitproblem = LSR_NOLIMITPROBLEM;
	    }

	    merge_search_results(tk_sr->srr_next, op_sr);
	}
	
	oper_extract(on);

	if((tk->referred_st == NULL_ST) && (tk->tk_state == TK_PASSIVE) && (tk->tk_operlist == NULLOPER))
	{
	    task_conn_extract(tk);
	    task_result(tk);
	    task_extract(tk);
	}
    }
}

subtask_error_wakeup(on)
struct oper_act	* on;
{
    struct task_act	* tk;
    struct ds_search_task	**next_st;
    struct ds_search_task	* st;

    DLOG(log_dsap, LLOG_TRACE, ("subtask_error_wakeup"));

    if((tk = on->on_task) == NULLTASK)
    {
	LLOG(log_dsap, LLOG_EXCEPTIONS, ("Oper can't wake up (error) extracted task"));
	oper_extract(on);
    }
    else
    {
	/*
	*  Were waiting for a remote subtask result and got a remote error.
	*  If it is a referral, then rechain the operation if appropriate
	*  otherwise dump the subtask and check the task for completion.
	*/
	ds_error_free(&(tk->tk_resp.resp_err));
	tk->tk_error = &(on->on_resp.resp_err);
	tk->tk_resp.resp_type = RESP_TYPE_RET;

	if((on->on_resp.resp_err.dse_type == DSE_DSAREFERRAL)
  	|| (on->on_resp.resp_err.dse_type == DSE_REFERRAL))
	{
	    DLOG(log_dsap, LLOG_DEBUG, ("Try rechaining st"));
	    if(oper_rechain(on) == OK)
	    {
		DLOG(log_dsap, LLOG_DEBUG, ("Succeeded rechaining st"));
		return;
	    }
	    DLOG(log_dsap, LLOG_DEBUG, ("Failed rechaining st"));
	    add_cref2poq (&tk->tk_resp.resp_res.dcr_dsres.res_sr, on->on_resp.resp_err.ERR_REFERRAL.DSE_ref_candidates);
	    on->on_resp.resp_err.ERR_REFERRAL.DSE_ref_candidates = NULLCONTINUATIONREF;
	}

	next_st = &(tk->referred_st);
	for(st=tk->referred_st; st!=NULL_ST; st=(*next_st))
	{
	    if(st == on->on_subtask)
		break;

	    next_st = &(st->st_next);
	}
	if(st == NULL_ST)
	{
	    LLOG(log_dsap, LLOG_EXCEPTIONS, ("subtask_result_wakeup - subtask lost from referred list"));
	}
	else
	{
	    st_comp_free (st);
	    (*next_st) = st->st_next;
	}

	oper_extract(on);

	if((tk->referred_st == NULL_ST) && (tk->tk_state == TK_PASSIVE) && (tk->tk_operlist == NULLOPER))
	{
	    task_conn_extract(tk);
	    task_result(tk);
	    task_extract(tk);
	}
    }
}

subtask_fail_wakeup(on)
struct oper_act	* on;
{
    struct task_act	* tk;
    struct DSError	* err;
    struct ds_search_task	**next_st;
    struct ds_search_task	* st;

    DLOG(log_dsap, LLOG_TRACE, ("subtask_fail_wakeup"));

    if((tk = on->on_task) == NULLTASK)
    {
	LLOG(log_dsap, LLOG_FATAL, ("subtask_fail_wakeup: no task"));
	oper_extract(on);
	return;
    }
    else
    {
	next_st = &(tk->referred_st);
	for(st=tk->referred_st; st!=NULL_ST; st=(*next_st))
	{
	    if(st == on->on_subtask)
		break;

	    next_st = &(st->st_next);
	}
	if(st == NULL_ST)
	{
	    LLOG(log_dsap, LLOG_EXCEPTIONS, ("subtask_result_wakeup - subtask lost from referred list"));
	}
	else
	{
	    add_cref2poq (&tk->tk_resp.resp_res.dcr_dsres.res_sr, st->st_cr);
	    st_comp_free (st);
	    (*next_st) = st->st_next;
	}

	oper_extract(on);

	if((tk->referred_st == NULL_ST) && (tk->tk_state == TK_PASSIVE) && (tk->tk_operlist == NULLOPER))
	{
	    task_conn_extract(tk);
	    task_result(tk);
	    task_extract(tk);
	}
    }
    err = &(tk->tk_resp.resp_err);
    if((err->dse_type != DSE_REFERRAL) && (err->dse_type != DSE_DSAREFERRAL))
    {
	err->dse_type = DSE_SERVICEERROR;
	err->ERR_SERVICE.DSE_sv_problem = DSE_SV_UNAVAILABLE;
    }
}

subtask_dsa_info_wakeup(di)
struct di_block	* di;
{
    struct task_act	* tk = di->di_task;


    DLOG(log_dsap, LLOG_TRACE, ("subtask_dsa_info_wakeup"));

    /*
    * Were waiting for a reference to return.
    * Check if the reference now returned is acceptable.
    * If it is return a referral and unwrap everything,
    * otherwise try another di_block for the reference.
    */

    if(di2cref(di, &(tk->tk_resp.resp_err), tk->tk_conn->cn_ctx) != OK)
    {
	LLOG(log_dsap, LLOG_EXCEPTIONS, ("subtask_dsa_info_wakeup - reference not acceptable"));
	/* Remove di_block which generated unwanted referral wait */
	if(tk->tk_dsas == NULL_DI_BLOCK)
	{
	    /* No more dsas from which to request info to form referral */
	    tk->tk_resp.resp_err.dse_type = DSE_SERVICEERROR;
	    tk->tk_resp.resp_err.ERR_SERVICE.DSE_sv_problem = DSE_SV_INVALIDREFERENCE;
	    task_conn_extract(tk);
	    task_error(tk);
	    task_extract(tk);
	    return;
	}
	return;
    }

    task_conn_extract(tk);
    task_error(tk);
    task_extract(tk);
}


add_cref2poq (res,cref)
struct ds_search_result *res;
ContinuationRef cref;	
{
ContinuationRef cr;	

	if (res->CSR_cr == NULLCONTINUATIONREF) {
		res->CSR_cr = cref;
		return;
	}
	for (cr = res->CSR_cr; cr->cr_next != NULLCONTINUATIONREF; cr=cr->cr_next)
		;

	cr->cr_next = cref;
}