/*
 * This module provides auxillary support functions for the DECNET_ACCESS
 * module, namely construction of task specifications from a default and
 * script directory prefix.
 *
 * The module is initialized by calling dnetx_initialize() with the name
 * of a DECnet task (e.g. WWWEXEC) or a logical that translates to
 * a task specification.  This provides the default task specification used
 * by dnetx_parse_task().
 *
 * The default task name logical may specify a search list of nodes in
 * one of 2 ways:
 *
 *	$ DEFINE taskname "node1::""0=taskname""",node2::,node3::,...
 *
 *	$ DEFINE taskname "node_list::""0=taskname"""
 *	$ DEFINE node_list node1::,node2::,node3::,...
 *
 * Additional targets defined by dnetx_define_task() must use the second
 * method (logical node name) to specify a search list.
 *
 * int dnetx_initialize ( default_taskname );
 * int dnetx_define_task ( char *logical_task );
 * int dnetx_parse_task 
 *	( char *logical_task, int *task_len, char ***tasklist, int *rr_point );
 *
 * Author:	David Jones
 * Date:	 6-AUG-1994
 * Revised:	2-NOV-1995	Pedantic fixes, remove & in front of arrays.
 * Revised:	14-MAR-1998	Add *_connection functions.
 * Revised:	27-MAR-1998	Add timeouts and permanent server limits
 *				to save_connection functions.
 * Revised:	 7-DEC-1999	Increase stack for saved_list_monitor to
 *				fix problem with VAX/VMS 7.2.
 * Revised:	11-JAN-2000	Name pthread objects
 */
#include "pthread_1c_np.h"
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <descrip.h>
#include <iodef.h>
#include <lnmdef.h>

#include "tutil.h"
#include "decnet_searchlist.h"	/* validate prototypes against actual */
#include "decnet_access.h"
int tlog_putlog();
int http_log_level;				/* global variable */
/*
 * Global (module-wide) variables, Initialized by dnetx_initialize.
 */
static int dnet_defnode;			/* Length of node portion */

struct task_block {
    struct task_block *next;
    int rr_point;
    int log_task_len;			/* Length of log_task value */
    char log_task[256];			/* Original task name */
    char *asn_task[128];		/* List of logicals */
};
static struct task_block dnet_default_task;	/* First block in list */
static struct task_block *dnet_task_defs_end;	/* Last block in list */

struct saved_cnx_block {		/* Holds saved connection info */
    struct saved_cnx_block *flink, *blink;
    char *taskname;			/* Task spec string to match */
    void *cnx;
    int state;				/* 0-permanent, 1-temporary */
    struct timespec timeout;
};
typedef struct saved_cnx_block *saved_cnx;
static struct saved_cnx_block saved_cnx_list;	/* active list, circular */
static struct saved_cnx_block *free_saved_cnx;  /* free list (1 link) */
static struct saved_cnx_block *oldest_temporary;
static int saved_cnx_alloc_size;		/* Expansion size */
static int saved_cnx_permanent_count;		/* Current number of perm cnx */
static int saved_cnx_permanent_limit;		/* permanent limit */
static int saved_cnx_timeout;
#define DEFAULT_SAVED_CNX_TIMEOUT 300		/* 5 minutes */
#define MONITOR_THREAD_STACK 60000               /* Size size for monitor thread*/
static void *saved_list_monitor ( void *dummy );

static pthread_mutex_t dnet_cnx;		/* mutex for cnx list update */
static pthread_mutex_t dnet_task;		/* mutex for task list update*/
static pthread_cond_t dnet_abort_timeout;	/* signals list reshuffle */
/****************************************************************************/
/* Private routine to expand log_task string in task block to list of 
 * assigned tasks.  If name_override is non-NULL, it will specify the
 * logical name to attempt to translate rather than the node name parsed
 * from tb->log_task.
 */
static int expand_logical_task ( char *name_override, struct task_block *tb )
{
    int length, flags, status, node_len, SYS$TRNLNM(), task_len, rr_point;
    int index, max_index, name_len, outndx;
    char *asn_spec, string[256];
    struct { short length, code; void *buf; void *retlen; } item[4];
    $DESCRIPTOR(table_name,"LNM$FILE_DEV");
    $DESCRIPTOR(log_name,"");
    /*
     * Default to asn_task list that consists solely of itself.
     */
    if ( http_log_level > 6 ) 
	tlog_putlog ( 7, "Expanding logical task: '!AZ'!/", tb->log_task );
    tb->asn_task[0] = tb->log_task;
    tb->asn_task[1] = (char *) 0;
   /*
     * Parse the first element (assumed to be node) out of the logical name
     */
    for ( node_len = name_len = 0; tb->log_task[node_len]; node_len++ ) {
	if ( tb->log_task[node_len] == ':' ) { 
	    if ( name_len == 0 ) name_len = node_len;
	    if ( tb->log_task[node_len+1] == ':' ) node_len++; 
            node_len++;
	    break; 
	}
	if ( tb->log_task[node_len] == '"' ) name_len = node_len;;
    }
    if ( node_len == 0 ) return 1;
    if ( name_override ) {
	/* Override name to use for translation */
        if ( http_log_level > 6 ) 
	    tlog_putlog ( 0, "Translation override: !AZ!/", name_override );
        log_name.dsc$w_length = tu_strlen ( name_override );
        log_name.dsc$a_pointer = name_override;
    } else {
        log_name.dsc$w_length = name_len;
        log_name.dsc$a_pointer = tb->log_task;
    }
    /*
     * Attempt to translate logical names.  Make item list for $TRNLNM.
     */
    flags = LNM$M_CASE_BLIND;
    length = 0;
    item[0].length = sizeof(int); item[0].code = LNM$_INDEX;
    item[0].buf = &index; item[0].retlen = (void *) 0;

    item[1].length = sizeof(string)-1; item[1].code = LNM$_STRING;
    item[1].buf = string; item[1].retlen = &length;

    item[2].length = sizeof(int); item[2].code = LNM$_MAX_INDEX;
    item[2].buf = (void *) &max_index; item[2].retlen = (void *) 0;

    item[3].length = item[3].code = 0;	/* terminate list */
    max_index = 0;
    /*
     * Call TRNLNM for every equivalence name, first call will get
     * number of translations (max_index).
     */
    for ( index = outndx = 0; index <= max_index; index++ ) {
        status = SYS$TRNLNM ( &flags, &table_name, &log_name, 0, item );
	if ( (status&1) == 0 ) {
	    return 1;
	}
	if ( length > 0 ) {
	    /*
	     * Make asn_list entry by replacing node portion of log_name
	     * with the equivalence string.
	     */
	    if ( string[length-1] != '"' ) {
	        tu_strnzcpy 
		    ( &string[length], &tb->log_task[node_len], 255-length);
	        length = tu_strlen ( string );
	    }
	    tb->asn_task[outndx] = malloc ( length+1 );
	    tb->asn_task[outndx+1] = (char *) 0;
	    tu_strnzcpy ( tb->asn_task[outndx], string, length );
	    outndx++;
        }
	    
        item[2].length = item[2].code = 0;	/* new  list termination */
    }
    return 1;
}    
 /****************************************************************************/
/* Initialize decnet module, assume we are called from the initial thread
 * before any other threads are started.
 */
int dnetx_initialize ( char *default_taskname )
{
    int status, i, LIB$GET_EF(), SYS$TRNLNM();
    int flags, length, max_index;
    char *tmp;
    struct { short length, code; void *buf; void *retlen; } item[4];
    $DESCRIPTOR(table_name,"LNM$FILE_DEV");
    $DESCRIPTOR(log_name,"");
    pthread_t monitor;
    pthread_attr_t monitor_attr;
    /*
     * Initialize decthreads objects.
     */
    status = INITIALIZE_MUTEX ( &dnet_task );
    SET_MUTEX_NAME(&dnet_task,"OSU DECnet task")
    status = INITIALIZE_MUTEX ( &dnet_cnx );
    SET_MUTEX_NAME(&dnet_task,"OSU DECnet connection")
    status = INITIALIZE_CONDITION ( &dnet_abort_timeout );
    SET_COND_NAME(&dnet_abort_timeout,"OSU DECnet GC timer")
    /*
     * Zero list of saved connections and allocate free list.
     */
    saved_cnx_alloc_size = 8;
    free_saved_cnx = (saved_cnx) 0;
    saved_cnx_list.flink = saved_cnx_list.blink = &saved_cnx_list;
    saved_cnx_list.state = 0;		/* 'permanent' connection */
    saved_cnx_permanent_count = 0;
    saved_cnx_permanent_limit = 0;
    saved_cnx_timeout = DEFAULT_SAVED_CNX_TIMEOUT;
    if ( saved_cnx_alloc_size > 0 ) {
	saved_cnx blk;
	int i;
	LOCK_C_RTL
	blk = (saved_cnx) malloc ( saved_cnx_alloc_size *
		sizeof(struct saved_cnx_block) );
	UNLOCK_C_RTL
	if ( blk ) {		/* allocation succeeded */
            free_saved_cnx = blk;
	    for (i = 0; i < saved_cnx_alloc_size-1; i++) 
		blk[i].flink = &blk[i+1];
	    blk[saved_cnx_alloc_size-1].flink = (saved_cnx) 0;
	}
    }
    /*
     * Create thread to manage purging the saved list of old temporaries.
     */
    status = INITIALIZE_THREAD_ATTR ( &monitor_attr );
    status = pthread_attr_setstacksize ( &monitor_attr, MONITOR_THREAD_STACK );
#ifdef PTHREAD_USE_D4
    status = pthread_create ( &monitor, monitor_attr,
		(pthread_startroutine_t) saved_list_monitor, (void *) 0 );
#else
    if ( status == 0 ) {
	typedef void * (*startroutine_t)(void *);
	status = pthread_create ( &monitor, &monitor_attr, 
		(startroutine_t) saved_list_monitor, (void *) 0 );
    }
#endif
    /*
     * Attempt to translate default_taskname (i.e. WWWEXEC) as a logical name.
     */
    flags = LNM$M_CASE_BLIND;
    max_index = length = 0;
    item[0].length = sizeof(dnet_default_task.log_task)-1; 
    item[0].code = LNM$_STRING;
    item[0].buf = dnet_default_task.log_task; item[0].retlen = &length;

    item[1].length = sizeof(int); item[1].code = LNM$_MAX_INDEX;
    item[1].buf = (void *) &max_index; item[1].retlen = (void *) 0;
    item[2].length = item[2].code = 0;	/* terminate list */

    log_name.dsc$w_length = tu_strlen ( default_taskname );
    log_name.dsc$a_pointer = default_taskname;

    status = SYS$TRNLNM ( &flags, &table_name, &log_name, 0, item );
    if ( (status&1) == 1 ) {
	/*
	 * Leave translation as default string in block, terminate it.
	 */
	dnet_default_task.log_task[length] = '\0';
    } else {
	/*
	 * No translation, construct one of form: 0::"0='default_taskname'".
	 */
	tu_strncpy ( dnet_default_task.log_task, "0::\"0=", 6 );
	tu_strnzcpy ( &dnet_default_task.log_task[6], default_taskname,
		sizeof(dnet_default_task.log_task)-8 );
	tu_strcpy ( &dnet_default_task.log_task[tu_strlen(
		dnet_default_task.log_task)], "\"" );
    }
    dnet_default_task.log_task_len = tu_strlen (dnet_default_task.log_task);
    dnet_default_task.next = (struct task_block *) 0;
    dnet_default_task.rr_point = 0;
    /*
     * Expand either our translation or the default_taskname if it
     * has multiple value.
     */
    if ( max_index > 0 )
        status = expand_logical_task ( default_taskname, &dnet_default_task );
    else
	status = expand_logical_task ( (char *) 0, &dnet_default_task );
    /*
     * Determine length of node portion of default taskname including colons.
     */
    for ( dnet_defnode = i = 0; dnet_default_task.log_task[i]; i++ ) {
	if ( (dnet_default_task.log_task[i] == ':') && 
		(dnet_default_task.log_task[i+1] == ':') ) {
	    dnet_defnode = i+2;
	    break;
	}
    }
    /*
     * initialize task definition list to end with default task block;
     */
    dnet_task_defs_end = &dnet_default_task;
    return status;
}
/****************************************************************************/
/* Initialize DECnet task search list block and add to list.  We assume this
 * routine is called from the initial thread before any other threads
 * are created.
 *
 * Return value:	0	Malloc error on task block.
 *			1	Normal.
 */
int dnetx_define_task ( char *logical_task )
{
    int length, status, node_len, task_len, rr_point;
    char *asn_spec, **task_list, string[256];
    struct task_block *tb;
    /*
     * See if task already defined, use side effect to determine
     * length of node/object portion.
     */
    if ( dnetx_parse_task ( logical_task, &task_len, &task_list, &rr_point) ) {
	return 3;	/* already defined */
    }
    /*
     * Allocate task block block and add to list.
     */
    tb = malloc ( sizeof(struct task_block) );
    if ( !tb ) return 0;
    dnet_task_defs_end->next = tb;
    dnet_task_defs_end = tb;
    /*
     * Initialize all of task block except for asn_list and set up default
     * asn_list to be the logical_task string itself.
     */
    tb->next = (struct task_block *) 0;
    tb->rr_point = 0;
    tu_strnzcpy ( tb->log_task, logical_task, task_len );
    if ( (task_len == 0) || (logical_task[task_len-1] == ':') ) {
        tu_strnzcpy ( &tb->log_task[task_len],
		&dnet_default_task.log_task[dnet_defnode],
		sizeof(tb->log_task)-task_len-1 );
    }
    tb->log_task_len = tu_strlen ( tb->log_task );
    /*
     * Translate logical task to list of assigned tasks.
     */
    status = expand_logical_task ( (char *) 0, tb );
    return status;
}
/****************************************************************************/
/*
 * Parse of script directory specifition for node and object info
 * construct list task specifcations for use with dnet_connect.
 * of the parsed string is returned in.
 */
int dnetx_parse_task ( char *logical_task,	/* Script-dir string */
	int *prefix_len,			/* chars used from script-dir*/
	char ***task_list,			/* List of expanded task specs*/
	int *rr_point )				/* Preferred starting point */
{
    int i, node, status, length;
    char *bindir, *task_spec, ovr_spec[256];
    struct task_block *tb;
    /*
     * Construct initial DECnet task specification, using www_exec_task
     * as the default and overriding with script_dir prefix.
     */
    bindir = logical_task;
    *prefix_len = 0;
    length = 0;
    task_spec = dnet_default_task.log_task;

    for ( i = 0; bindir[i] && (i < 128); i++ ) {
	if ( (bindir[i] == ':') && (bindir[i+1] == ':') ) {
	    /*
	     * Script dir has node, see if object ("obj=") specified as well 
	     */
	    i = i + 2;			/* length of node + "::" */
	    task_spec = ovr_spec;
	    if ( bindir[i] == '"' ) {
		/* bindir has both, override. */
		for ( i = i + 1; bindir[i] && (i < 127); i++ )
		    if ( bindir[i] == '"' ) { i++; break; }
		tu_strnzcpy ( task_spec, bindir, i );
	    } else {
		/*
		 * Only override node portion of dnet_default_task, get
		 * object portion from default task string.
		 */
		tu_strncpy ( ovr_spec, bindir, i );
		tu_strnzcpy ( &task_spec[i], 
			&dnet_default_task.log_task[dnet_defnode],
				255 - i );
	    }
	    /*
	     * Return prefix length to caller and determine length of
	     * full task with defaults applied.
	     */
	    *prefix_len = i;
	    length = tu_strlen ( task_spec );
	    break;
	}
    }
    if ( http_log_level > 6 ) tlog_putlog ( 7, 
	"task specification after default: '!AZ' prefix_l=!SL, bindir: '!AZ'!/", 
		task_spec, *prefix_len, bindir);
    /*
     * Now we have task_spec, search list of defined tasks for matching
     * specification and return it's expanded list to caller.
     */
    for ( tb = &dnet_default_task; tb; tb = tb->next ) {
	if ( http_log_level > 7 ) tlog_putlog (8,
		"Testing against '!AF'!/", tb->log_task_len, tb->log_task );

	if ( (task_spec == dnet_default_task.log_task) ||
		((length == tb->log_task_len) &&
		(0 == tu_strncmp ( tb->log_task, task_spec, length ))) ) {
	    /*
	     * We found block, update Round Robin point and return.
	     * Only dick with rr_point if more than one in list.
	     */
	    if ( tb->asn_task[1] ) {
    		status = pthread_mutex_lock ( &dnet_task );
	        *rr_point = tb->rr_point + 1;
	        if ( !tb->asn_task[*rr_point] ) *rr_point = 0;
	        tb->rr_point = *rr_point;
		status = pthread_mutex_unlock ( &dnet_task );
	    } else {
		*rr_point = 0;
	    }
	    *task_list = tb->asn_task;
	    return 1;
	}
    }
    /*
     * Return error value.
     */
    return 0;
}
/*****************************************************************************/
/* The following routines manage connection reuse.  Return values are 1 for
 * success and 0 for failure.
 */
int dnetx_set_saved_limits ( int max_saved, int max_perm, int time_limit )
{
    pthread_mutex_lock ( &dnet_cnx );
    saved_cnx_permanent_limit = max_perm;
    saved_cnx_timeout = time_limit;
    pthread_mutex_unlock ( &dnet_cnx );
    return 1;
}
int dnetx_save_connection ( void *dptr, char *task_name )
{
    saved_cnx blk;
    int status;
    /*
     * Keep from running down connection when thread exits.
     */
    status = dnet_disown_context ( dptr );
    if ( (status&1) == 0 ) return 0;
    /*
     * Allocate block from global free list.
     */
    if ( pthread_mutex_lock ( &dnet_cnx ) ) {
	tlog_putlog(0,"Error locking mutex for saved_connection!/" );
    }
    blk = free_saved_cnx;               
    if ( !blk ) {
	/*
	 * Free list empty, expand it by alloc_size ammount, then increase size
	 */
	int i;
	LOCK_C_RTL
	blk = (saved_cnx) malloc ( saved_cnx_alloc_size *
		sizeof(struct saved_cnx_block) );
	UNLOCK_C_RTL
	if ( http_log_level > 4 ) tlog_putlog ( 5,
	    "Expanded saved cnx list by !SL, block addr: !XL!/",
	    saved_cnx_alloc_size, blk );
	if ( blk ) {
	    /* Link into free list */
	    for (i=0; i < saved_cnx_alloc_size-1; i++) blk[i].flink = &blk[i+1];
	    blk[saved_cnx_alloc_size-1].flink = (saved_cnx) 0;
	    saved_cnx_alloc_size += 16;	/* take more next time */
	}
    }
    if ( !blk ) {
	pthread_mutex_unlock ( &dnet_cnx );
	dnet_claim_context ( dptr );		/* restore ownership */
	return 0;		/* no free blocks */
    }
    free_saved_cnx = blk->flink;
    /*
     * Add block to global saved list.  List is doubly link circular (no NULLs).
     */
    blk->taskname = task_name;
    blk->cnx = dptr;
    if ( saved_cnx_permanent_count < saved_cnx_permanent_limit ) {
	/*
	 * Permanently keep connection open.  Push to front of list for
	 * preferred reuse.
	 */
	saved_cnx_permanent_count++;
	blk->flink = saved_cnx_list.flink;
	blk->blink = &saved_cnx_list;
        blk->state = 0;
	saved_cnx_list.flink->blink = blk;
	saved_cnx_list.flink = blk;
    } else {
	/*
	 * Save connection for limited time (state=1), determined by global
	 * timeout value.  Add connection to tail of list.
	 */
	struct timespec delta;
        blk->flink = &saved_cnx_list;
        blk->blink = saved_cnx_list.blink;
	blk->state = 1;
	saved_cnx_list.blink->flink = blk;
	saved_cnx_list.blink = blk;
	delta.tv_sec = saved_cnx_timeout;
	delta.tv_nsec = 0;
	pthread_get_expiration_np ( &delta, &blk->timeout );
	if ( oldest_temporary == &saved_cnx_list ) {
	    /*
	     * Monitor thread is currently idle, signal it so it
	     * begins timeout of this block.
	     */
	    pthread_cond_signal ( &dnet_abort_timeout );
	}
    }
    pthread_mutex_unlock ( &dnet_cnx );
    return 1;
}

int dnetx_unsave_connection ( void *dptr )
{
    saved_cnx blk;
    /*
     * Search for block with matching data pointer.
     */
    pthread_mutex_lock ( &dnet_cnx );
    for (blk=saved_cnx_list.flink; blk != &saved_cnx_list; blk=blk->flink) {
	if ( blk->cnx == dptr ) {
	    /*
	     * If block was temporary connection, cleanup timeout.
	     */
	    if ( blk->state == 0 ) {
		saved_cnx_permanent_count--;
	    } else if ( blk == oldest_temporary ) {
		/* This connection is front of timeout list, kill the timeout */
		pthread_cond_signal ( &dnet_abort_timeout );
	    }
	    /*
	     * Move block from active list to free list.
	     */
	    blk->flink->blink = blk->blink;
	    blk->blink->flink = blk->flink;
	    blk->flink = free_saved_cnx;
	    blk->state = 0;
	    free_saved_cnx = blk;

	    break;
	}
    }
    pthread_mutex_unlock ( &dnet_cnx );
    if ( blk != &saved_cnx_list ) {
	dnet_claim_context ( dptr );
	return 1;
    } else return 0;		/* not found on list */
}
int dnetx_find_connection ( char **tasklist, int rr_point, int *first,
	void **dptr, char **result_task )
{
    saved_cnx blk;
    if ( *first < 0 ) {
	/*
	 * Search active list for connection with task name on exec list.
	 * Note we only test addresses.
	 */
        pthread_mutex_lock ( &dnet_cnx );
	for (blk=saved_cnx_list.flink; blk != &saved_cnx_list; 
		blk=blk->flink) {
	    int i;
	    for (i=0; tasklist[i]; i++) if (tasklist[i] == blk->taskname ) {
		if ( blk->state == 0 ) {
		    saved_cnx_permanent_count--;
		} else if ( blk == oldest_temporary ) {
		    /* 
		     * The monitor thread is waiting for a timeout on this
		     * block.  Issue signal that will let monitor start
		     * new wait on next block (state is changed to 0 below).
		     */
		    pthread_cond_signal ( &dnet_abort_timeout );
		}
		/*
		 * Remove from saved list and return connection pointer.
		 */
		*dptr = blk->cnx;
		*result_task = blk->taskname;
		blk->flink->blink = blk->blink;
		blk->blink->flink = blk->flink;
		blk->flink = free_saved_cnx;
		blk->state = 0;			/* 'permanent' */
		free_saved_cnx = blk;
		pthread_mutex_unlock ( &dnet_cnx );
	        dnet_claim_context ( *dptr );
		return 1;
	    }
	}
	pthread_mutex_unlock ( &dnet_cnx );
	/*
	 * No match on saved list, return at round robin point.
	 */
	*first = rr_point;
	*dptr = (void *) 0;		/* No match */
	*result_task = tasklist[rr_point];
	return 1;
    }
    /*
     * Not initial call, return next spot in RR list.
     */
    (*first)++;
    *dptr = (void *) 0;				/* not a saved connection */
    if ( !tasklist[*first] ) *first = 0;	/* wrap to beginning */
    if ( *first == rr_point )	return 0;	/* all tested */
    *result_task = tasklist[*first];

    return 1;
}
/***************************************************************************/
/* This routine is the start routine for a thread created to manage timeouts
 * of temporary connections.
 *
 * Synchonization:
 *     Monitor thread waits for oldest temporary thread to expire (earliest
 *     expiration), setting global variable oldest_temporary to address of
 *     the connection block.  A client reusing the connection will change the
 *     status of the block and signal dnet_abort_timeout.
 */
static void *saved_list_monitor ( void *dummy )
{
    saved_cnx blk;
    pthread_t self;
    int status;

    self = pthread_self();
    DETACH_THREAD(self);
    SET_THREAD_NAME(self,"OSU DECnet monitor")
    /*
     * Main loop, thread always holds dnet_cnx except for when waiting for
     * signal.
     */
    pthread_mutex_lock ( &dnet_cnx );
    for ( ; ; ) {
	/*
	 * Scan for oldest temporary connection, converting old temporaries to 
	 * permanent if possible.
	 */
	for ( blk = saved_cnx_list.flink; blk != &saved_cnx_list;
		blk = blk->flink ) {
	    if ( blk->state == 1 ) {
		if ( saved_cnx_permanent_count < saved_cnx_permanent_limit ) {
		    blk->state = 0;
		    saved_cnx_permanent_count++;
		} else {
		    blk->state = 2;	/* flag with wait in progress */
		   break;
		}
	    }
	}
	oldest_temporary = blk;
        /*
         * Wait for signal from client thread that block is being used OR
	 * for a timeout on the oldest block.
	 */
	if ( http_log_level > 4 ) tlog_putlog ( 5, 
		"Saved_connection: waiting !AZ, timeout=!SL!/", blk->state == 2 ?
		"for oldest timeout" : "for new connection", saved_cnx_timeout );
	if ( blk->state == 2 ) 
	    status = pthread_cond_timedwait ( &dnet_abort_timeout, &dnet_cnx,
			&blk->timeout );
	else
	    status = pthread_cond_wait ( &dnet_abort_timeout, &dnet_cnx );
	if ( http_log_level > 4 ) tlog_putlog ( 5,
		"Saved_connection: wait completed with status: !SL/!SL!/",
		status, blk->state );
        /*
         * A zero status means client thread signalled us, non-zero indicates
	 * a timeout.
         */
	if ( (status != 0) && (blk->state == 2) ) {
	    /*
	     * Wait timed out and block hasn't been moved by client thread.
	     * Move block from active list to free list.
	     */
	    void *dptr;
	    blk->flink->blink = blk->blink;
	    blk->blink->flink = blk->flink;
	    blk->flink = free_saved_cnx;
	    dptr = blk->cnx;
	    blk->state = 0;
	    free_saved_cnx = blk;
	    /*
	     * close connection.  Release locks while doing so.
	     */
	    pthread_mutex_unlock ( &dnet_cnx );
	    if ( 1&dnet_claim_context ( dptr ) ) dnet_disconnect ( dptr );
	    pthread_mutex_lock ( &dnet_cnx );
	}
    }
    /*
     * Above loop never exits.
     */
    pthread_mutex_unlock ( &dnet_cnx );

    return (void *) 0;
}
