/*
 * This pthread module creates a thread that listens on a specified TCP port
 * for incoming connections.  Each connection received creates a new thread
 * to handle the session.  You may call ts_declare_port multiple times to
 * listen on more than one TCP port.  On thread exit, the connection is
 * closed.
 *
 * If a connection is received and no client contexts are available, the
 * listener thread will wait for up to three seconds for a context
 * to free up.
 *
 * int ts_declare_tcp_port ( port_num, client_limit, attr, thread, start );
 *	int start ( ctx, port, remote_address, index, remaining );
 * int ts_set_manage_port ( port_num, host_address, callback );
 *	int callback ( ctx, port, shutdown_time );
 * int ts_set_logging ( callback );
 *	int callback ( min_level, faostr, ... );
 * int ts_tcp_write ( ctx, buffer, length );
 * int ts_tcp_read ( ctx, buffer, maxlen, *length );
 * int ts_tcp_close ( ctx );
 * int ts_tcp_info ( local_port, remote_port, remote_address, remote_host );
 * char *ts_tcp_remote_host();
 * int ts_tcp_transaction_count ( ctx );
 * int ts_tcp_end_transaction ( ctx );
 * int ts_set_local_addr ( address );
 *
 * Revised: 26-MAR-1994		Added CMU IP support.
 * Revised:  7-JUN-1994		Removed AST_SAFE stuff, DEC documented
 *				the cond_signal_int behaviour.
 * Revised: 20-JUL-1994		Cleanup use of startup_status.
 * Revised: 22-JUL-1994		Add code to stall for connection if no clients.
 * Revised: 23-JUL-1994		Added ts_tcp_remote_host() function (UCX only).
 * Revised:  8-AUG-1994		Fixed bug in ts_tcp_remote_host(), accvio on
 *				lookup failure.
 * Revised:  8-AUG-1994		Fixed bug in ts_tcp_remote_host(), missing arg.
 *				tu_strnzcpy call, CMUTCP section.
 * Revised: 15-AUG-1994		Added support for TCPWARE interface.
 * Revised: 20-AUG-1994		Added manage port.
 * Revised: 23-AUG-1994		Work around Multinet include file bugs.
 * Revised: 24-SEP-1994		Remove direct reference to tlog_putlog.
 * Revised:  3-NOV-1994		Add keepalive option to UCX sockets.
 * Revised: 12-JAN-1995		Add George Carrette's (gjc@village.com) mods
 *				to support multihomed hosts.
 * Revised: 3-MAR-1995		Try to make CMUTCP variant of accept_connect()
 *				more robust.
 * Revised: 7-APR-1995		Work around problem with management host.
 * Revised: 19-APR-1995		Add missing semicolon (CMUTCP variant).
 * Revised: 27-MAY-1995		Deassign control channel on port shutdown.
 * Revised: 29-MAY-1995		Add BSDTCP option (substitutes
 *				tserver_tcp_sockets.c)
 * Revised: 15-JUL-1995		Use common client pools for listen ports.
 * Revised: 23-JAN-1996		Use more robust signalling with shared client
 *				pools.
 * Revised: 16-FEB_1995		Add ts_set_time_limit() function.
 * Revised:  1-MAR-1995		re-use per-client condition variables.
 * Revised: 19-MAR-1996		Fix bug in end_transaction - failed to init
 *				variable.
 * Revised: 10-APR-1996		Another try at dealing with the CMU driver bugs.
 * Revised: 13-may-1996		Fix in TCPware QIO_AND_WAIT call.
 * Revised: 19-JUN-1996		Fix apparent bug in previous CMU fix.
 *				Remove level 15 PUTLOG calls from critical
 *				paths in ts_tcp_read and ts_tcp_write.
 * Revised: 25-JUL-1996		More fully initialize dummy client context used
 *				for manage port operations - allows remote
 *				address to be retrieved.
 * Revised: 20-SEP-1996		parameterize listen backlog.
 * Revsised: 13-FEB-1997	Modify declare_port so that when mutli-homing
 *				any of the listen threads can shutdown the
 *				server.
 * Revised:  18-FEB-1997	Check for JPI$_MULTITHREAD.
 * Revised:  23-MAR-1997	Add 'scavenge' hack:  if timelimit is set to
 *				a negative number, subsequent I/O to that
 *				client by be aborted should additional client
 *				threads be needed.
 * Revseid:  2-APR-1997		Add parent back pointer to client context,
 *				allowing more efficient location of port
 *				context for rundown operations.
 * Revised: 12-APR-1997		Scavenge threads when running down port.
 * Revised: 19-MAY-1997		Cast start routine in pthread_create() calls.
 * Revised: 3-SEP-1997		Fix race condition in port_manage() on SMP
 *				machines.
 * Revised: 5-DEC-1997		Skip time limits on management requests if
 *				more than 1 listen thread.
 *				Force process exit if listen thread detects
 *				failure of client threads to begin execution.
 * Revsied: 22-DEC-1997		ensure set_time_limit resets scavenge_eligible
 *				flag for non-scavengable (positive) time limits
 * Revsied: 9-JAN-1998		Check for pending connections when adding to
 *				scavenge list and make connection timeout
 *				immediately.
 * Revised: 13-MAY-1998		Fix bug in port_rundown, don't detach thread
 *				if main thread will be joining with it.
 * Revsied: 13-OCT-1998		More cleanup on port_rundown routine.
 * Revised:  7-APR-1999		Patch for UCX 5.0: zero sockname struct.
 * Revised:  5-AUG-1999		Add hack to tcp_info to return TCP vendor.
 * Revisd:   14-AUG-1999	Add hack to work with broken 5.0 ucx$inetdef.
 * Revised:   6-DEC-1999	Increase size of listener thread stacks to
 *				accommodate VAX/VMS 7.2.  Change QIOWs to use
 *				ENF.
 * Revised:  30-DEC-1999	Support @listen_backlog=n for set_local_address
 *				function.
 * Revised:  10-MAY-2001	Add diagnositcs to listener failure.
 * Revised:  16-MAY-2001	Work around UCX 5.1 bug.
 * Revised:  1-AUG-2001		Fix problem with Multinet build caused by 16 
 *				May fix.
 * Revised:  7-SEP-2001		Parameterize write segment size (
 *				@segment_size=nnn), set default to 2K.
 */
#ifdef BSDTCP
/* Include alternate source file to completly replace code in module */
#include "tserver_tcp_sockets.c"

#else					/* Goes to EOF */
#include "pthread_1c_np.h"
#include "tutil.h"
#define DEFAULT_LISTEN_BACKLOG 5
#define DEFAULT_WRITE_SEGMENT_LIMIT 2048

/* Default to use UCX */
#if !defined(CMUTCP) && !defined(TWGTCP) && !defined(MULTINET)
#if !defined(UCXTCP) && !defined(TCPWARE)
#define UCXTCP
#endif
#endif

#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <descrip.h>
#include <iodef.h>
int SYS$DASSGN(), SYS$CANCEL();

#ifdef UCXTCP
#include <ssdef.h>			/* VMS system service codes */
#include <errno.h>			/* C/unix error codes */
#define TCPSEL(ucx,mlt,twg,cmu,twr) ucx
#include <UCX$INETDEF.H>
#define sockaddr_in SOCKADDRIN
#ifdef TCPIP$INETDEF_LOADED
/* TCPIP 5.0 changed the definitions in ucx$inetdef. */
#define SOCKADDRIN _SOCKADDRIN
#ifndef UCX$C_AF_INET
#define UCX$C_AF_INET TCPIP$C_AF_INET
#endif
#ifndef UCX$C_SOCKOPT
#define UCX$C_SOCKOPT TCPIP$C_SOCKOPT
#endif
#ifndef UCX$C_REUSEADDR
#define UCX$C_REUSEADDR TCPIP$C_REUSEADDR
#endif
#ifndef UCX$C_KEEPALIVE
#define UCX$C_KEEPALIVE TCPIP$C_KEEPALIVE
#endif
#endif
#endif

#ifdef MULTINET
#define TCPSEL(ucx,mlt,twg,cmu,twr) mlt
#include "MULTINET_ROOT:[MULTINET.INCLUDE.SYS]TYPES.H"
#include "MULTINET_ROOT:[MULTINET.INCLUDE.NETINET]IN.H"
#include "MULTINET_ROOT:[MULTINET.INCLUDE.SYS]SOCKET.H"
#include "MULTINET_ROOT:[MULTINET.INCLUDE.VMS]INETIODEF.H"
#include "MULTINET_ROOT:[MULTINET.INCLUDE]NETDB.H"
/* #include "MULTINET_ROOT:[MULTINET.INCLUDE]errno.h" */
#endif
#ifdef TWGTCP
#define TCPSEL(ucx,mlt,twg,cmu,twr) twg
#include "TWG$COMMON:[NETDIST.INCLUDE.SYS]TYPES.H"
#include "TWG$COMMON:[NETDIST.INCLUDE.SYS]NETDB.H"
#include "TWG$COMMON:[NETDIST.INCLUDE.SYS]SOCKET.H"
#include "TWG$COMMON:[NETDIST.INCLUDE.NETINET]IN.H"
#include "TWG$COMMON:[NETDIST.INCLUDE.VMS]INETIODEF.H"
#endif
#ifdef TCPWARE
#define TCPSEL(ucx,mlt,twg,cmu,twr) twr
#include "tcpip_include:types.h"
#include "tcpip_include:in.h"
#include "tcpip_include:socket.h"
#include "tcpip_include:inet.h"
#include "tcpip_include:inetiodef.h"
#include "tcpip_include:netdb.h"
#include "tcpip_include:sockerr.h"
#endif
#ifdef CMUTCP
#define TCPSEL(ucx,mlt,twg,cmu,twr) cmu
#include <socket.h>
#include <in.h>
#define CMU_LISTEN_ADDRESS "0.0.0.0"
#define CMU_LISTEN_FLAGS 0
#define CMU_CLOSE_FLAGS 0
#define CMU_WRITE_FLAGS 0
#else
#define CMU_WRITE_FLAGS 0
#endif
#ifndef EPERM
#include <errno.h>
#endif
/* we have our own macros because we may not be able to trust
   the reentrancy of all vendor-supplied helper functions */

#define HTONS(_x) (((_x)/256)&255) | (((_x)&255)<<8);

#define ERROR_FAIL(s,text) if (s != 0) perror(text);
#define PORT_THREAD_STACK 60000

typedef struct client_context client_ctx_struct, *client_ctx;
struct client_context {
    struct port_context *flink, *blink;
    struct port_context *parent;		/* 'owner' port */
    struct client_context *slink;		/* scavenge list */
    int index, port_num;
    pthread_t thread;
    int status;					/* 0-new, 1-active, 2-deleted */
    int transaction_count;
    int *stack_top;		/* Top address (nearly) of thread's stack */
    pthread_cond_t io_done;
    int (*start)( client_ctx ctx, 
	  short port, unsigned char *rem_addr,
	  int ndx, int length);	/* Application supplied startup routine */
    struct timespec expiration;	/* expiration time for timed I/O */
    int time_limit;		/* 0 - none, 1 - pending, 2 - expired */
    int scavenge_eligible;	/* if true, timed I/O may be aborted */
    int preserve;		/* if true, do not deassign chan on rundown */
    short chan;			/* Network channel */
    short dns_chan;		/* Channel for name lookups or -1 */
    unsigned long local_ip_addr;
    int clients_remaining;
    short iosb[4];

    struct sockaddr_in remote_address;     /* Remote address */
    int hn_status;		/* Status of hostname item: 0, 1, -1 */
    char hostname[256];		/* Remote host name */
#ifdef NAME_PTHREAD_OBJECTS
    char thread_name[32];
    char cond_name[32];		/* name for debugging */
#endif
};

struct client_pool {			/* Manage pool of clients */
    struct client_pool *flink;
    pthread_attr_t *client_attr;	/* Attributes for created clients */
    pthread_cond_t client_available;	/* signals freelist is non-empty */
    int ref_count;			/* Number of ports sharing pool */
    int connect_pending;		/* True if thread waiting */
    int client_count, client_limit;
    int cond_highwater;			/* highest client index valid io_done */
    client_ctx free_clients;
    client_ctx scavenge_first, scavenge_last;
#ifdef NAME_PTHREAD_OBJECTS
    char name[64];			/* name for debugging */
#endif
};

struct port_control {
    int shutdown;		/* true if shutdown signaled */
    int exit_status;
    pthread_cond_t port_exit;
};
struct port_context {
    struct port_context *flink, *blink;
    int port_num;
    int status;
    pthread_t thread;
    unsigned long local_ip_addr;
    short chan, fill;
    int (*start)( client_ctx ctx, 
	  short port, unsigned char *rem_addr,
	  int ndx, int length);	/* Application supplied startup routine */
    int client_count;
    int delete_pending;
    struct client_pool *pool;
    struct port_control *shutdown_monitor;
    client_ctx_struct client_list;		/* place blank stuff at end */
#ifdef NAME_PTHREAD_OBJECTS
    char thread_name[64];			/* name for debugging */
    char cond_name[64];
#endif
};
typedef struct port_context port_ctx_struct, *port_ctx;
#define ts_client_ctx client_ctx
#include "tserver_tcp.h"		/* validate header file */

static port_ctx_struct port_list;
static int ts_startup_client ( client_ctx ctx );
port_ctx tserver_tcp_port_list;
static int ts_listen_backlog = 0;
static int ts_write_segment_limit = 0;
#define CB_SIZE 128
static struct hn_cache_block {
    unsigned long address;
    int status;
    char hostname[256];
} cb[CB_SIZE];
static ts_manage_port;		/* Remote port number to induce shutdowns */
static unsigned int ts_manage_host;/* Remote address to induce port shutdown */
static int (*ts_manage_callback)(ts_client_ctx ctx, short port, int
		*shutdown_time);  /* upcall for management connects */
static int ts_logging = 0;
static int (*ts_putlog)(int, char *, ...);	/* upcall for logging */
static int local_addr_count;		/* Number of defined addresses */
static int local_addr_alloc;		/* allocated size of list. */
static unsigned long *local_addr_list;	/* local listen address, stored in 
				network byte order */
/*
 * The following mutexes and conditions serialize access to resources:
 *    tcp_io		This mutex is used before SYS$QIO calls. 
 *			Thread-specific condition variables are then used
 *			in conjunction with an AST to wait on the I/O.
 *    tcp_ctl		This mutex is obtained when a thread needs to modify
 *			the client list or port list data structures.
 *    port_startup	Mutex to serialize access to port startup/init routine.
 *    port_startup_done	Condition used in conjuction with port_startup.
 */
static pthread_mutex_t tcp_io;		/* tcp/io data mutex */
static pthread_mutex_t tcp_ctl;		/* tcp context database mutex */
static pthread_mutex_t port_startup;	/* serialize port startup */
static pthread_cond_t port_startup_done;  /* condition */
static int startup_status;		/* Predicate */

static pthread_key_t port_key;		/* Private data for port listener */
static pthread_key_t client_key;	/* Private data for TCP client */
static pthread_once_t ts_mutex_setup = PTHREAD_ONCE_INIT;

static void ts_init_mutexes();		/* forward reference */
static int ts_synch_ast(), ts_ios();
static int ts_synch_io ( int status, pthread_cond_t *done, short *iosb );
static int ts_synch_client ( int status, client_ctx ctx );
static int create_listen_socket(int, unsigned long, short, pthread_cond_t *);
typedef struct { int length; struct sockaddr_in a; } socket_info;
static int accept_connect ( int, int, int, pthread_cond_t *, socket_info * );
static int port_manage ( port_ctx, int, socket_info * );
/*************************************************************************/
#ifdef PTHREAD_USE_D4
/*
 * Draft 4 library lacks upcalls so always synchronize via ASTs signalling
 * a condition variable.
 */
int SYS$QIO();
#define QIO_AND_WAIT(cond,chan,func,iosb,p1,p2,p3,p4,p5,p6) \
	(pthread_mutex_lock(&tcp_io),\
	 ts_synch_io(SYS$QIO(0,chan,func,iosb,pthread_cond_signal_int_np,cond,\
		p1,p2,p3,p4,p5,p6),cond,(short *) iosb))
#else
/*
 * With standard V7 library, select synchronization method based upon whether
 * kernel thread upcalls enabled.
 */
#include <syidef.h>
#include <jpidef.h>
int SYS$QIOW(), SYS$QIO();
static int kernel_threads = 0;
#define ENF 128
#define QIO_AND_WAIT(cond,chan,func,iosb,p1,p2,p3,p4,p5,p6) \
 (kernel_threads ? \
     ts_ios(SYS$QIOW(ENF,chan,func,iosb,0,0,p1,p2,p3,p4,p5,p6), (short *) iosb) \
  :  (pthread_mutex_lock(&tcp_io),\
	 ts_synch_io(SYS$QIO(ENF,chan,func,iosb,pthread_cond_signal_int_np,\
		cond,p1,p2,p3,p4,p5,p6),cond,(short *) iosb)) )
typedef void * (*startroutine_t)(void *);
#endif
/*************************************************************************/
/* Set flag and putlog callback routine address.  Note that this routine
 * must only be called if the tcp_ctl mutex is owned by the current thread.
 */
int ts_set_logging ( int callback(int, char *, ...) )
{
    int previous_logging;
    /*
     * Initialize globals for module, only once though.
     */
    pthread_once ( &ts_mutex_setup, ts_init_mutexes );
    /*
     * Get mutex and update shared data.  Return value if previous setting.
     */
    pthread_mutex_lock ( &tcp_ctl );
    previous_logging = ts_logging;
    ts_putlog = callback;
    if ( ts_putlog ) ts_logging = 1; else ts_logging = 0;
    pthread_mutex_unlock ( &tcp_ctl );
    return previous_logging;
}
/*
 * Use this macro with care since it leaves an if dangling!
 */
#define PUTLOG if ( ts_logging ) (*ts_putlog)
/*************************************************************************/
/* make special thread for listening for a port exit.  An exiting port will
 * set the exit_status and signal port_exit, which this thread uses as an
 * impetus for it own exit.    A thread joining with this thread will then
 * see the thread exit.
 */
static int ts_shutdown_monitor ( struct port_control *ctx ) {
    int status;
    pthread_mutex_lock ( &tcp_ctl );
    while ( ctx->shutdown == 0 ) {
	status = pthread_cond_wait ( &ctx->port_exit, &tcp_ctl );
	if ( status ) break;
	
    }
    pthread_mutex_unlock ( &tcp_ctl );
    pthread_cond_destroy ( &ctx->port_exit );
    return ctx->exit_status;
}
/*************************************************************************/
/*
 * Setting the time limit sets a expiration time beyond which all subsequent
 * I/O's are prohibited/aborted (i.e. it is not a timeout for each I/O).
 * Limit value is number of seconds from current time, set to 0 cancel.
 */
int ts_tcp_set_time_limit ( client_ctx ctx, int limit )
{
    if ( limit != 0 ) {
	struct timespec delta;
	if ( limit < 0 ) {
	    ctx->scavenge_eligible = 1;
	    limit = (-limit);
	} else ctx->scavenge_eligible = 0;	/* ensure reset */
	delta.tv_sec = limit;
	delta.tv_nsec = 0;
	if ( 0 == pthread_get_expiration_np ( &delta, &ctx->expiration ) ) {
	    ctx->time_limit = 1;	/* time limit now active */
	} else {
	    PUTLOG ( 0, "Time computation problem in ts_set_time_limit()!/" );
	    ctx->time_limit = 0;	/* no expiration */
	    return 0;
	}
    } else ctx->time_limit = 0;		/* no expiration */
    return 1;
}
/*************************************************************************/
/* Parse option string.
 */
static int set_tcp_option ( char *option )
{
    char keyword[256], *value;
    int i;
    /*
     * Copy to local buffer and parse into keyword/value.
     */
    tu_strnzcpy ( keyword, option, sizeof(keyword)-1 );
    value = tu_strstr ( keyword, "=" );
    if ( !value || (value == &keyword[0]) ) {
	PUTLOG ( 0, "Invalid TCP option request: '!AZ' (not keyword=value)!/",
		 keyword );
	return 20;
    } else {
	*value++ = '\0';
	tu_strupcase ( keyword, keyword );
    }
    /*
     * Take requested action.
     */
    if ( tu_strncmp ( keyword, "LISTEN_BACKLOG", tu_strlen(keyword) ) == 0 ) {
	ts_listen_backlog = atoi ( value );
	PUTLOG(5,"Set listen backlog to !SL!/", ts_listen_backlog );
    } else if ( tu_strncmp ( keyword, "SEGMENT_SIZE", 
		tu_strlen(keyword) ) == 0 ) {
	ts_write_segment_limit = atoi ( value );
	PUTLOG(5,"Set listen backlog to !SL!/", ts_write_segment_limit );
	if ( ts_write_segment_limit > 32768 ) ts_write_segment_limit = 32768;
	if ( ts_write_segment_limit < 256 ) ts_write_segment_limit = 256;
	if ( ts_write_segment_limit != atoi ( value ) )
	   PUTLOG(0, "Invalid segment size, set to !SL!/",
		ts_write_segment_limit );
    } else {
	PUTLOG(0,"Unrecognized TCP option: '!AZ'!/", keyword );
	return 20;
    }
    return 1;
}
/*************************************************************************/
/* Set local listen address to bind sockets to.  Note that local_ip_addr
 * must be in local byte order.   Address must be numeric form.
 *
 * Parameters other that the listen address may also be set with this
 * routine by beginning the addr string with '@'.  Options:
 *
 *    @listen_backlog=n
 */
int ts_set_local_addr ( char *addr ) 
{
    unsigned long a[4], *address;
    unsigned char octet[sizeof(unsigned long)];
    int j;
    /*
     * Initialize globals for module, only once though.
     */
    pthread_once ( &ts_mutex_setup, ts_init_mutexes );
    /*
     * Check for escape.
     */
    if ( addr[0] == '@' ) {
	return set_tcp_option ( &addr[1] );
    }
    /*
     * Decode address, IP addresses are defined as 4 octets so it is
     * already in network data order when cast to a long.
     */
    LOCK_C_RTL
    memset(a,0,sizeof(a));
    sscanf(addr,"%d.%d.%d.%d",&a[0],&a[1],&a[2],&a[3]);
    UNLOCK_C_RTL
    for ( j = 0; j < 4; j++ ) octet[j] = a[j];
    address = (unsigned long *) octet;
    /*
     * Get mutex and update shared data.
     */
    pthread_mutex_lock ( &tcp_ctl );
    if ( local_addr_count == 1 && local_addr_list[0] == 0 )
	local_addr_list[0] = *address;
    else {
	if ( local_addr_count >= local_addr_alloc ) {
	    local_addr_alloc = local_addr_alloc + 32;
	    LOCK_C_RTL
	    local_addr_list = realloc ( local_addr_list,
		sizeof(unsigned long) * local_addr_alloc );
	    UNLOCK_C_RTL
	}
	local_addr_list[local_addr_count++] = *address;
    }
    pthread_mutex_unlock ( &tcp_ctl );
    PUTLOG ( 5, "Address !AZ encodes as !XL!/", addr, *address );
    return 1;			/* Always return success */
}
/*************************************************************************/
/* Initialize port management parameters.  Connects from specified port
 * and address will be handled by callback routine rather than creating
 * a normal client thread.
 */
int ts_set_manage_port ( int port_num, unsigned int host_address,
	int callback(client_ctx ctx, short port, int *shutdown_time) )
{
    /*
     * Initialize globals for module, only once though.
     */
    pthread_once ( &ts_mutex_setup, ts_init_mutexes );
    /*
     * Get mutex and update shared data.  Convert port number to network
     * data order so we can compare it directly to remote sockaddr.
     */
    pthread_mutex_lock ( &tcp_ctl );
    ts_manage_port = HTONS(port_num);
    ts_manage_host = host_address;
    ts_manage_callback = callback;
    /*
     * If we have an explicit listen address (multi-homed host),
     * it is impossible to get messages from the loopback address,
     * so use the listen address instead for the management host.
     */
    if ( local_addr_list[0] && (ts_manage_host == 0x0100007F) ) 
	ts_manage_host = local_addr_list[0];
    pthread_mutex_unlock ( &tcp_ctl );
    return 1;
}
/*************************************************************************/
/* Create thread that listens on a particular TCP/IP port and starts
 * new thread for each accepted connection.  A caller supplied start routine
 * is called by the created thread to process the session.
 *
 * This routine can be called multiple times within an application to listen
 * on different ports.  Each port gets it's own client limit.
 *
 * User start (session) routine:
 *	int start ( void *ctx, int port_num, sockaddrin remote );
 */
int ts_declare_tcp_port ( 
    int port_num, 		/* Port number to listen on, host order */
    int client_limit, 		/* Limit on concurrent client threads allowed */
    pthread_attr_t *client_attr, /* Thread attributes for client threads */
    pthread_t *control_thread, 	/* Thread that listens for connects */
    int (*start)( client_ctx ctx, short port, unsigned char *rem_addr,
		int ndx, int length) )	/* Start routine for new clients. */
{
    int ts_startup_port(), i, stacksize, status;
    port_ctx ctx, tmp;
    struct client_pool *pool;
    struct port_control *shutdown_ctx;
    pthread_t listen_thread;
    pthread_attr_t port_attr;
    static int marker = 0;
    struct sched_param param;
    /*
     * Initialize globals for module, only once though.
     */
    pthread_once ( &ts_mutex_setup, ts_init_mutexes );
    /*
     * Find or create client pool structure for managing contexts.
     */
    if ( client_limit > 0 ) {
	/*
	 * Allocate new pool.
	 */
        pool = (struct client_pool *) malloc ( sizeof(struct client_pool) );
	pool->client_attr = client_attr;
	pool->ref_count = 0;
        pool->connect_pending = 0;
	pool->client_count = 0;
        pool->client_limit = client_limit;
        pool->scavenge_first = pool->scavenge_last = (client_ctx) 0;
        INITIALIZE_CONDITION(&pool->client_available);
	/*
         * Pre-allocate client_limit number client contexts for the client threads.
         */
        pool->free_clients = (client_ctx) 0;
        LOCK_C_RTL
	pool->cond_highwater = marker;
#ifdef NAME_PTHREAD_OBJECTS
	sprintf(pool->name,"OSU Client avail(%d..%d)", marker,
		marker+pool->client_limit-1);
	SET_COND_NAME(&pool->client_available,pool->name)
	sprintf(pool->name,"OSU Client avail/%d..%d", /* test if copied */
		marker,marker+pool->client_limit-1);
#endif
        for ( i = 0; i < pool->client_limit; i++ ) {
	    client_ctx new;
	    new = (client_ctx) malloc ( sizeof(client_ctx_struct) );
	    new->index = marker+(pool->client_limit-i);
	    new->dns_chan = -1;
            new->hn_status = 0;
	    new->flink = (port_ctx) pool->free_clients;
	    pool->free_clients = new;
        }
        marker += pool->client_limit;	/* base for thread index numbers */
        UNLOCK_C_RTL
    } else {
	/*
	 * Lock port list and scan ports for pool with matching client_attr 
	 * address.
	 */
	pthread_mutex_lock ( &tcp_ctl );
	for ( tmp = port_list.flink; tmp != &port_list; tmp = tmp->flink ) {
	    if ( tmp->pool->client_attr == client_attr ) {
	        pool = tmp->pool;
	        break;
	    }
	}
        pthread_mutex_unlock ( &tcp_ctl );
	if ( tmp == &port_list ) return 20;
    }
    /*
     * Create thread attributes object for the listener thread(s).  Use FIFO
     * scheduling which will give it a higher priority than the client
     * threads which have round robin scheduling.
     */                                                              
    status = INITIALIZE_THREAD_ATTR ( &port_attr );
    ERROR_FAIL(status,"Port thread attribute create failure" )
    status = pthread_attr_setinheritsched ( &port_attr, PTHREAD_EXPLICIT_SCHED );
    status = SET_ATTR_SCHEDPOLICY ( &port_attr, SCHED_FIFO );
    param.sched_priority  = (PRI_FIFO_MIN+PRI_FIFO_MAX)/2;
    status = SET_ATTR_SCHEDPARAM ( &port_attr, &param );
    status = pthread_attr_setstacksize ( &port_attr, PORT_THREAD_STACK );
    /*
     * create special shutdown monitor thread if multi-homing, allowing
     * any of the threads to shutdown.
     */
    if ( local_addr_count > 1 ) {
	LOCK_C_RTL
	shutdown_ctx = (struct port_control *) 
		malloc ( sizeof(struct port_control) );
	UNLOCK_C_RTL
	shutdown_ctx->shutdown = 0;
	shutdown_ctx->exit_status = 0;
	INITIALIZE_CONDITION(&shutdown_ctx->port_exit);
	SET_COND_NAME(&shutdown_ctx->port_exit,"OSU TCP port_exit")
	
#ifdef PTHREAD_USE_D4
        status = pthread_create ( control_thread, port_attr,
		(pthread_startroutine_t) ts_shutdown_monitor, shutdown_ctx );
#else
        status = pthread_create ( control_thread, &port_attr, 
		(startroutine_t) ts_shutdown_monitor, shutdown_ctx );
#endif
	PUTLOG(3,"shutdown monitor thread create status: !SL!/", status );
        ERROR_FAIL(status,"Port shutdown monitor thread create failure" )
    } else shutdown_ctx = (struct port_control *) 0;
    /*
     * Create listener thread for each local address in list.
     */
    for ( i = 0; i < local_addr_count; i++ ) {
        /*
         * Allocate port control block that becomes the thread-specific context
         * data for the new thread.
         */
        LOCK_C_RTL
        ctx = (port_ctx) malloc ( sizeof(port_ctx_struct) );
        UNLOCK_C_RTL
        /*
         * Initialize pertinent fields in control block.
         */
        ctx->status = 0;
        ctx->start = start;
        ctx->client_count = 0;
        ctx->pool = pool;
	ctx->shutdown_monitor = shutdown_ctx;
	ctx->delete_pending = 0;
        /*
         * Link the control block into the global list and connect pool, use 
         * mutex to synchronize update of pointers.
         */
        pthread_mutex_lock ( &tcp_ctl );
        ctx->port_num = port_num;
        ctx->local_ip_addr = local_addr_list[i];
        PUTLOG(3,"declaring local address: !XL!/", ctx->local_ip_addr );
        ctx->flink = &port_list;
        ctx->blink = port_list.blink;
        port_list.blink->flink = ctx;
        port_list.blink = ctx;
        pool->ref_count++;
        pthread_mutex_unlock   ( &tcp_ctl );
        /* 
         * Create thread and block until it finishes initializing so we can
         * check it's status.
         */
        status = pthread_mutex_lock ( &port_startup );
        ERROR_FAIL(status,"Port startup lock failure" )
        startup_status = 0;

#ifdef PTHREAD_USE_D4
        status = pthread_create ( shutdown_ctx ? &listen_thread : 
		control_thread,	port_attr, 
		(pthread_startroutine_t) ts_startup_port, ctx);
#else
        status = pthread_create ( shutdown_ctx ? &listen_thread :
		control_thread, &port_attr, (startroutine_t) ts_startup_port, 
		ctx);
#endif
        ERROR_FAIL(status,"Port thread create failure" )

        while ( startup_status == 0 ) {
            status = pthread_cond_wait ( &port_startup_done, &port_startup );
            ERROR_FAIL(status,"Port startup wait failure" )
        }
        pthread_mutex_unlock ( &port_startup );
    }

    return ctx->status;
}
/**************************************************************************/
/* Write data to thread's TCP/IP connection.
 */
int ts_tcp_write 
    ( client_ctx ctx,		/* Context passed to port start() function */
    char *buffer,		/* Data buffer to write */
    int length )		/* Number of chars to write */
{
    int status, remaining, seg;
    /*
     * Make $QIO call and block until AST signals us.
     */
    status = 1;
    for ( remaining = length; remaining > 0; remaining -= seg ) {
	/*
	 * Only write up to 2 K at a time.
	 */
	seg = remaining > ts_write_segment_limit ? 
		ts_write_segment_limit : remaining;
        if ( ctx->time_limit == 2 ) status = 556;
        else {
#ifdef PTHREAD_USE_D4
            pthread_mutex_lock ( &tcp_io );
            status = SYS$QIO ( 11, ctx->chan, IO$_WRITEVBLK, ctx->iosb, 
	        pthread_cond_signal_int_np, &ctx->io_done, 
	        buffer, seg, 0, CMU_WRITE_FLAGS, 0, 0 );
	    status = ts_synch_client ( status, ctx );
#else
	    if ( kernel_threads && !ctx->time_limit ) {
                status = SYS$QIOW ( ENF, ctx->chan, IO$_WRITEVBLK, ctx->iosb, 0, 0,
	            buffer, seg, 0, CMU_WRITE_FLAGS, 0, 0 );
	        if ( (status&1) == 1 ) status = ctx->iosb[0];
            } else {
                pthread_mutex_lock ( &tcp_io );
                status = SYS$QIO ( ENF, ctx->chan, IO$_WRITEVBLK, ctx->iosb, 
	            pthread_cond_signal_int_np, &ctx->io_done, 
		    buffer, seg, 0, CMU_WRITE_FLAGS, 0, 0 );
	        status = ts_synch_client ( status, ctx );
            }
#endif
	}
        if ( (status&1) == 0 ) break;

#ifndef CMUTCP
	seg = ctx->iosb[1];	/* length actually sent */
#endif
	buffer = &buffer[seg];   /* Point to next unsent char */
    }
    return status;
}
/**************************************************************************/
/* Read data from thread's TCP/IP connection.
 */
int ts_tcp_read
    ( client_ctx ctx,		/* Context passed to port start() function */
    char *buffer,		/* Data buffer to write */
    int maxlen,			/* Size of buffer */
    int *length )		/* Number of chars read */
{
    int status;
    /*
     * Make $QIO call and block until AST signals us.
     */
    if ( ctx->time_limit == 2 ) status = 556;
    else {
#ifdef PTHREAD_USE_D4
        pthread_mutex_lock ( &tcp_io );
        status = SYS$QIO ( 11, ctx->chan, IO$_READVBLK, ctx->iosb, 
	    pthread_cond_signal_int_np, &ctx->io_done, 
	    buffer, maxlen, 0, 0, 0, 0 );
	status = ts_synch_client ( status, ctx );
#else
	if ( kernel_threads && !ctx->time_limit ) {
            status = SYS$QIOW ( ENF, ctx->chan, IO$_READVBLK, ctx->iosb, 0, 0,
	        buffer, maxlen, 0, 0, 0, 0 );
	    if ( (status&1) == 1 ) status = ctx->iosb[0];
        } else {
            pthread_mutex_lock ( &tcp_io );
            status = SYS$QIO ( ENF, ctx->chan, IO$_READVBLK, ctx->iosb, 
	        pthread_cond_signal_int_np, &ctx->io_done, 
		buffer, maxlen, 0, 0, 0, 0 );
	    status = ts_synch_client ( status, ctx );
        }
#endif
    }
    if ( (status&1) == 1 ) {
	/*
	 * Return length read to caller, some TCP packages treat EOF as
	 * a successful zero-length read, convert these cases.
	 */
	*length = ctx->iosb[1];
#if defined(TWGTCP) || defined(MULTINET) || defined(TCPWARE)
	if ( *length == 0 ) status = 2160;
#endif
    } else *length = 0;
    return status;
}
/**************************************************************************/
/* Perform synchronous disconnect of socket.  Do not deassign channel.
 * THe WIN/TCP and MULTINET interface does not have an explicit close 
 * other than deassigning the channel, so do nothing for that case.
 */
int ts_tcp_close
    ( client_ctx ctx )		/* Context passed to port start() function */
{
    int status;
    /*
     * Make $QIO call and block until AST signals us.
     */
    if ( ctx->preserve ) return 3;	/* null operation */
    status = 1;
#ifdef UCXTCP

    status = QIO_AND_WAIT ( &ctx->io_done, ctx->chan, IO$_DEACCESS, &ctx->iosb,
	    0, 0, 0, 0, 0, 0 );
#endif
#ifdef CMUTCP
    /* Use _DELETE function to gracefully close connection, sending
     * all data. */
    status = QIO_AND_WAIT( &ctx->io_done, ctx->chan, IO$_DELETE, &ctx->iosb,
	    CMU_CLOSE_FLAGS, 0, 0, 0, 0, 0 );
#endif
    return status;
}
/**************************************************************************/
/* Do one-time initialization of static variables for this module, primarily
 * mutexes, condition variables and thread-specific context keys.
 */
static void port_rundown(), client_rundown();
static void ts_init_mutexes ( )
{
    int status, i;
#ifndef PTHREAD_USE_D4
    int syi_code, syi_value, jpi_code, jpi_value, LIB$GETJPI(), LIB$GETSYI();
    /*
     * Determine synch mode for QIO_AND_WAIT
     */
    kernel_threads = 0;
#ifdef __ALPHA			/* only alphas support kernel threads */
    syi_code = SYI$_MULTITHREAD;
    status = LIB$GETSYI ( &syi_code, &syi_value, 0, 0, 0 );
    if ( ((status&1)==1) && (syi_value > 0) ) {
	/* OS support for kernel threads enabled, see if process has it */
	jpi_code = JPI$_MULTITHREAD;
	jpi_value = 0;
	status = LIB$GETJPI ( &jpi_code, 0, 0, &jpi_value, 0, 0 );
	if ( ((status&1)==1) && (jpi_value > 0) ) kernel_threads = 1;
    }
#endif
#endif
    /*
     * Create mutexes and condition variables.
     */
    status = INITIALIZE_MUTEX ( &tcp_io );
    ERROR_FAIL(status,"Error creating tcp_io mutex" )
    SET_MUTEX_NAME(&tcp_io,"OSU TCP IOLOCK")

    status = INITIALIZE_MUTEX ( &tcp_ctl ); 
    ERROR_FAIL(status,"Error creating tcp_ctl mutex" )
    SET_MUTEX_NAME(&tcp_ctl,"OSU TCP listener DB")

    status = INITIALIZE_MUTEX ( &port_startup );
    ERROR_FAIL(status,"Error creating port_startup mutex" )
    SET_MUTEX_NAME(&port_startup,"OSU TCP port_startup")

    status = INITIALIZE_CONDITION ( &port_startup_done );
    ERROR_FAIL(status,"Error creating port_startup_done condition variable" )
    SET_COND_NAME(&port_startup_done,"OSU TCP port_startup")
    /* 
     * Create keys used to locate context blocks 
     */
    status = CREATE_KEY ( &port_key, port_rundown );
    ERROR_FAIL(status,"Error creating port listener context key" )
    SET_KEY_NAME(port_key,"OSU TCP port_rundown")

    status = CREATE_KEY ( &client_key, client_rundown );
    ERROR_FAIL(status,"Error creating tcp client context key" )
    SET_KEY_NAME(client_key,"OSU TCP client_rundown")
    /*
     * Initialize list head of port contexts created.  The pointers in
     * this structure are protected by the tcp_ctl mutex.
     */
    tserver_tcp_port_list = &port_list;		/* set global pointer */
    pthread_mutex_lock ( &tcp_ctl );
    port_list.flink = &port_list; port_list.blink = &port_list;
    port_list.port_num = 0;	/* flag list header */
    pthread_mutex_unlock ( &tcp_ctl );
    /*
     * Initialize cache for name lookups and manage port parameters.
     */
    for ( i = 0; i < CB_SIZE; i++ ) cb[i].status = 0;
    local_addr_count = 1;
    local_addr_alloc = 16;
    LOCK_C_RTL
    local_addr_list = (unsigned long *) 
	malloc ( sizeof(unsigned long)*local_addr_alloc );
    UNLOCK_C_RTL
    local_addr_list[0] = 0;
    ts_manage_port = -1;
    ts_manage_host = 0;
}
/****************************************************************************/
/* The following routines manage the scavenge list for a client thread pool.
 * If a client context is flagged as scavenge_eligible, it is added to the 
 * scavenge list when a timed I/O is initiated and when the I/O is completed.
 * A listen thread may call reclaim_scavenge_list() to force these pending
 * I/O's to abort.
 *
 * The calling thread must hold the tcp_io mutex prior to calling
 * add_scavenge_list or remove_scavenge_list to avoid a race condition
 * between SYS$QIO and reclaim_scavenge_list's use SYS$CANCEL().
 */
static void add_to_scavenge_list ( client_ctx ctx )
{
    struct client_pool *pool;
    pool = ctx->parent->pool;

    if ( pool->connect_pending ) {
        /* connect pending, update timeout value to already expired */
	struct timespec delta;
	delta.tv_sec = 0; delta.tv_nsec = 0;
	pthread_get_expiration_np ( &delta, &ctx->expiration );
    }
    if ( pool->scavenge_first ) {
	pool->scavenge_last->slink = ctx;
    } else {
	pool->scavenge_first = ctx;
    }
    ctx->slink = (client_ctx) 0;
    pool->scavenge_last = ctx;
}
static void remove_from_scavenge_list (	client_ctx ctx )
{
    client_ctx prev, cur;
    struct client_pool *pool;
    pool = ctx->parent->pool;

    if ( pool->scavenge_first == ctx ) {
	pool->scavenge_first = ctx->slink;
	return;
    }

    prev = pool->scavenge_first;
    for ( cur = prev->slink; cur; cur = cur->slink ) {
	if ( cur == ctx ) {
	    prev->slink = ctx->slink;
	    if ( cur == pool->scavenge_last ) pool->scavenge_last = prev;
	    return;
	}
	prev = cur;
    }
    /*
     * Getting here means not found on scavenge list.
     */
}

int reclaim_scavenge_list ( struct client_pool *pool, int max_cancel )
{
    client_ctx cur;
    int cancelled;

    cancelled = 0;			/* number of pending I/O's cancelled */
    pthread_mutex_lock ( &tcp_io );
    for ( cur = pool->scavenge_first; 
		cur && (cancelled < max_cancel); cur = cur->slink ) {
	cur->scavenge_eligible = 2;		/* indicate reclaimed */
	if ( !cur->iosb[0] ) { SYS$CANCEL ( cur->chan ); cancelled++; }
    }
    pthread_mutex_unlock ( &tcp_io );
    return cancelled;
}
/***************************************************************************/
/*  Allocate a client context for a port, waiting up to stall_seconds if
 *  none available (0 means no wait).  Failure returns null pointer.
 */
static client_ctx allocate_client ( struct client_pool *pool, 
	port_ctx ctx, int stall_seconds )
{
    int status;
    client_ctx new_client;
    /*
     * Allocate client context block from port's pool.  Grab mutex while
     * while playing with module-wide data structures.
     */
    pthread_mutex_lock ( &tcp_ctl );
    if ( ctx->delete_pending ) {		/* parent is shutting down */
	pthread_mutex_unlock ( &tcp_ctl );
	return (client_ctx) 0;
    }
    new_client = pool->free_clients;
    if ( (!new_client && stall_seconds) || pool->connect_pending ) {
	/*
	 * Give ourselves second chance to get client block, waiting
	 * up to stall_seconds seconds.   Stall is forced if someone else
	 * waiting as well to give them a fairer chance to get block.
	 */
	struct timespec delta, abstime;
	delta.tv_sec = stall_seconds;
	delta.tv_nsec = 0;
	new_client = (client_ctx) 0;
	if ( 0 == pthread_get_expiration_np ( &delta, &abstime ) ) {
	    PUTLOG ( 0, 
		"Thread list exhausted on port !SL, !%D reclaiming: !SL!/", 
			ctx->port_num, 0, reclaim_scavenge_list(pool,8) );
	    for ( status = 0; (status==0) && !pool->free_clients; ) {
	        pool->connect_pending++;
	        status = pthread_cond_timedwait 
		    ( &pool->client_available, &tcp_ctl, &abstime );
	        pool->connect_pending--;
		
	    }
	    new_client = pool->free_clients;
	    if ( !new_client ) {
		/*
		 * Stall was unsuccessful in returning threads.  Scan
		 */
		client_ctx tmp;
		int starting_clients;
		starting_clients = 0;
		for ( tmp = (client_ctx) ctx->client_list.flink; 
		    tmp != &ctx->client_list; tmp = (client_ctx) tmp->flink ) {
		    if ( tmp->status == 0 ) {
			struct sched_param param; int policy;
			starting_clients++;
#ifdef PTHREAD_USE_D4
			status = pthread_setscheduler ( tmp->thread, 
				SCHED_FIFO, PRI_FIFO_MAX );
#else
			status = pthread_getschedparam ( tmp->thread,
			    &policy, &param );
			if ( status == 0 && (policy != SCHED_FIFO) ) {
			    param.sched_priority = PRI_FIFO_MAX;
			    status = pthread_setschedparam ( tmp->thread,
				SCHED_FIFO, &param );
			    if ( status != 0 ) 
				PUTLOG(0,"Error boosting thread priority\n");
			    else starting_clients--;
			} else if ( status ) {
			    PUTLOG(0,"Error adjusting thread priority, lost thread\n");
			} else {
			    PUTLOG(0,"Thread failed to respond to priority boost\n");
			}
#endif
		    }
		}
		if ( starting_clients*2 > pool->client_limit ) {
#ifdef PTHREAD_USE_D4
#define kernel_threads -4
#endif
		    PUTLOG(0, "!SL client contexts stuck in startup state aborting, KT flag: !SL!/",
			starting_clients, kernel_threads );
		    exit(44);
		}
	    }
	 } else PUTLOG ( 0, "Time computation problem!/" );
    }
    /*
     * If we got block, initialize it and update pool and port context.
     */
    if ( new_client ) {
	pool->free_clients = (client_ctx) new_client->flink;
	pool->client_count++;
	ctx->client_count++;
	new_client->flink = (port_ctx) &ctx->client_list;
	new_client->blink = ctx->client_list.blink;
	new_client->parent = ctx;
	ctx->client_list.blink = (port_ctx) new_client;
	new_client->blink->flink = (port_ctx) new_client;
	new_client->status = 0;
	new_client->hn_status = 0;		/* host name unknown */
	new_client->port_num = ctx->port_num;
	new_client->clients_remaining = 
			pool->client_limit - pool->client_count;
	if ( new_client->index > pool->cond_highwater ) {
	    /*
	     * First use for this context block, allocate condition var.
	     * (Assumes blocks are initially pushed on free_list is rev. order).
	     */
	    pool->cond_highwater = new_client->index;
	    INITIALIZE_CONDITION ( &new_client->io_done );
#ifdef NAME_PTHREAD_OBJECTS
	    sprintf(new_client->thread_name,"OSU TCP client %d", new_client->index);
	    sprintf(new_client->cond_name,"OSU TCP io_done %d", new_client->index);
	    SET_COND_NAME(&new_client->io_done,new_client->cond_name)
#endif
	    PUTLOG(5,"New highwater for client index: !SL!/",new_client->index);
	}
     }

    pthread_mutex_unlock ( &tcp_ctl );
    return new_client;
}
/***************************************************************************/
/* Define VMS-specific routines to let us call system service routines
 * asynchronously.  Use ts_synch_ast as the ast argument to the system service
 * and a condition variable as the ast parameter.  The ts_synch_io routine
 * then blocks the thread waiting for the condition to be signalled.
 *
 * Note that ts_synch_io assumes tcp_io lock is held by the thread.  It is
 * released upon return.
 */
static int ts_synch_ast ( pthread_cond_t *done )
{
    int status;
    status = pthread_cond_signal_int_np ( done );

    return status;
}
static int ts_ios ( int status, short *iosb )
{ 
    if ( (status&1) == 1 ) status = *iosb; 
    return status; 
}
static int ts_synch_io ( int status, pthread_cond_t *done, short *iosb )
{
    /*
     * Only do wait if system service status OK.
     */
    if ( (status&1) == 1 ) {
        /*
         * Loop until predicate (iosb nozero) is true.
         */
	do {
	    status = pthread_cond_wait ( done, &tcp_io );
	    if ( status != 0 ) break;
        } while ( *iosb == 0 );

	status = *iosb;
#ifdef CMUTCP
	if ( status == 44 ) {		/* abort status */
	    int *ptr;
	    ptr = (int *) iosb;
	    status = ptr[1];		/* IPACP status for abort */
	}
#endif
#ifdef SHOW_ERROR
	if ( status < 0 ) printf("synch_io has negative status: %d\n", status );
#endif
    }
    pthread_mutex_unlock ( &tcp_io );
    return status;
}
static int ts_synch_client ( int status, client_ctx ctx )
{
    /*
     * Only do wait if system service status OK.
     */
    if ( (status&1) == 1 ) {
	/*
	 * wait is either timed or open.
	 */
	if ( ctx->time_limit ) {
	    if ( ctx->scavenge_eligible ) add_to_scavenge_list ( ctx );
	    do {
	        status = pthread_cond_timedwait ( &ctx->io_done, &tcp_io,
			&ctx->expiration );
		if ( status != 0 ) {
		     /*
		      * Timeout occurred, cancel I/O and wait on it to finish.
		      */
		    ctx->time_limit = 2;
		    if ( !ctx->iosb[0] ) SYS$CANCEL ( ctx->chan );
		    while ( !ctx->iosb[0] ) {
			if (pthread_cond_wait(&ctx->io_done, &tcp_io)) break;
		    }
		    break;
		}
            } while ( ctx->iosb[0] == 0 );
	    if ( ctx->scavenge_eligible ) remove_from_scavenge_list ( ctx );
	} else {
            /*
             * Loop until predicate (iosb nozero) is true.
             */
	    do {
	        status = pthread_cond_wait ( &ctx->io_done, &tcp_io );
	        if ( status != 0 ) break;
            } while ( ctx->iosb[0] == 0 );

	}
	status = ctx->iosb[0];
#ifdef CMUTCP
	if ( status == 44 ) {		/* abort status */
	    int *ptr;
	    ptr = (int *) ctx->iosb;
	    status = ptr[1];		/* IPACP status for abort */
	}
#endif
#ifdef SHOW_ERROR
	if ( status < 0 ) printf("client_synch has negative status: %d\n", status );
#endif
    }
    pthread_mutex_unlock ( &tcp_io );
    return status;
}
/***********************************************************************/
/*
 * Top level of thread for listening on ports  Upon entry, the calling
 * thread will be waiting on the port_startup_done condition.
 */
int ts_startup_port ( port_ctx ctx )
{
    int status, stacksize, i, err_seq, SYS$ASSIGN();
    int client_chan, SYS$ASSIGN();
    $DESCRIPTOR(tcp_device,TCPSEL("UCX$DEVICE","INET0:","INET:","IP:","INET0"));
    socket_info remote;
    pthread_cond_t io_done;
    client_ctx new_client;
    struct client_pool *pool;
    /*
     * Set listen backlog to default value is not explicitly overridden.
     */
    if ( ts_listen_backlog <= 0 ) ts_listen_backlog = DEFAULT_LISTEN_BACKLOG;
    if ( ts_write_segment_limit <= 0 ) 
	ts_write_segment_limit = DEFAULT_WRITE_SEGMENT_LIMIT;
    /*
     * Create context key for this thread and init to our context block address.
     * The port rundown routine can then cleanup.
     */
    pthread_setspecific ( port_key, ctx );
    ctx->thread = pthread_self();
    ctx->client_list.flink = (port_ctx) &ctx->client_list;
    ctx->client_list.blink = (port_ctx) &ctx->client_list;
    ctx->client_list.index = 0;
    /*
     * Assign control channel for listens.
     */
    ctx->chan = 0;
    ctx->status = SYS$ASSIGN ( &tcp_device, &ctx->chan, 0, 0 );
    if ( (ctx->status&1) == 1 ) {
        /*
	 * We found, device, create condition to synchronize I/O
	 */
	status = INITIALIZE_CONDITION ( &io_done );
#ifdef NAME_PTHREAD_OBJECTS
	sprintf(ctx->thread_name,"OSU TCP listener %x:%d", ctx->local_ip_addr,
		ctx->port_num );
	sprintf(ctx->cond_name,"OSU TCP port %x:%d io", ctx->local_ip_addr,
		ctx->port_num );
	SET_COND_NAME(&io_done,ctx->cond_name)
	SET_THREAD_NAME(ctx->thread,ctx->thread_name)
#endif
	/*
	 * Create socket and bind address that we will listen on.
	 */
	ctx->status = create_listen_socket 
		(  ctx->port_num, ctx->local_ip_addr, ctx->chan, &io_done );
    }
    else return ctx->status;
    /* 
     * Acquire startup mutex so we know creating thread is waiting on us, then
     * set status variable and signal that setup is done 
     */
    status = pthread_mutex_lock ( &port_startup );
    if ( status != 0 ) perror ( "port_startup thread mutex" );
    startup_status = ctx->status;
    ctx->status = 0;		/* No longer starting */
    status = pthread_mutex_unlock ( &port_startup );
    status = pthread_cond_signal ( &port_startup_done );
    /*
     * Now  listen for incoming connections.
     */
    pool = ctx->pool;
    err_seq = 0;
    for ( ; ; ) {
	int length, current_port;
	/*
	 * Assign channel and listen for connect on newly assigned channel.
	 */
        client_chan = 0;
	status = SYS$ASSIGN ( &tcp_device, &client_chan, 0, 0 );
	if ( (status&1) == 0 ) {
	     PUTLOG ( 0, "$ASSIGN to device !AZ failed: !SL\n",
		tcp_device.dsc$a_pointer, status );
	     break;
	}
	status = accept_connect ( ctx->port_num, ctx->chan, 
		client_chan, &io_done, &remote );
	if ( (status&1) == 0 ) {
	     err_seq++;
#ifdef UCXTCP
	     PUTLOG ( 0, 
		"!%D accept_connect call failed: !SL, iosb[1]: !UL, seq: !SL!/", 
		0, status, remote.a.SIN$W_FAMILY, err_seq );
#endif
	     SYS$DASSGN ( client_chan );
	     if ( err_seq > 5 ) break;
#ifdef UCXTCP
	     /* Retry for certain transient errors. (connection reset by peer)*/
	     if ( (status == SS$_CONNECFAIL) && 
		(remote.a.SIN$W_FAMILY == ECONNRESET) ) continue;
#endif
	     break;
	}
	err_seq = 0;		/* clear once sucessfull accept */
	/*
	 * See if connect is from privileged management port.  Note that 
	 * port number is in network data order.  Since ts_manage_port is
	 * a shared variable, get mutex before examinging it.
	 */
	current_port = remote.a.TCPSEL(SIN$W_PORT,sin_port,sin_port,
		sin_port, sin_port);
	if ( current_port == ts_manage_port ) {
	    /*
	     * Only accept from proper host.
	     */
	    unsigned int current_address;
	    current_address = remote.a.TCPSEL(SIN$L_ADDR,sin_addr.s_addr,
		sin_addr.s_addr,sin_addr.s_addr,sin_addr.s_addr);
	    if ( current_address == ts_manage_host ) {
		/*
		 * If port_manage returns a non-zero value, exit loop with that
		 * status.  Close client connection in all cases afterward.
		 */
		status = port_manage ( ctx, client_chan, &remote );
	        SYS$DASSGN ( client_chan );
		if ( status ) break;
		continue;
	    }
	}
	/*
	 * Allocate client context block from free list.
	 */
	new_client = allocate_client ( pool, ctx,  3 );
	/*
	 * Start new thread to process request if we got a client block.
	 */
	if ( new_client ) {
	    new_client->transaction_count = 0;
	    new_client->time_limit = 0;
	    new_client->scavenge_eligible = 0;
	    new_client->preserve = 0;
	    new_client->chan = client_chan;
	    new_client->start = ctx->start;
	    new_client->local_ip_addr = ctx->local_ip_addr;
	    new_client->remote_address = remote.a;

#ifdef PTHREAD_USE_D4
    	    status = pthread_create ( &new_client->thread, *(pool->client_attr),
		(pthread_startroutine_t) ts_startup_client, new_client );
#else
    	    status = pthread_create ( &new_client->thread, pool->client_attr,
		(startroutine_t) ts_startup_client, new_client );
#endif
	    if ( status != 0 ) perror ( "error creating client thread" );
	} else {
	    PUTLOG ( 0, "Connect reject, no free client contexts!/" );
	    SYS$DASSGN ( client_chan );
	}
    }
    /* Cleanup resources and return final status */
#ifndef CMUTCP
    SYS$DASSGN ( ctx->chan );
#endif
    pthread_cond_destroy ( &io_done );
    if ( ctx->shutdown_monitor ) {
	pthread_mutex_lock ( &tcp_ctl );
	if ( ctx->shutdown_monitor->shutdown == 0 ) {
	    ctx->shutdown_monitor->shutdown = 1;
	    ctx->shutdown_monitor->exit_status = status;
	    pthread_cond_signal ( &ctx->shutdown_monitor->port_exit );
	}
	pthread_mutex_unlock ( &tcp_ctl );
    }
    return status;
}

/***************************************************************************/
/* Handle management commands.  Read int value from remote client and
 * use that as timeout value for rundown.
 */
static int port_manage ( port_ctx ctx, int client_chan, socket_info *remote )
{
    int status, length, count, shutdown_time;
    struct timespec delta, abstime;
    static struct client_context mgr;
    static int mgr_initted = 0;
    struct client_pool *pool;
    char number[16];
    pthread_mutex_lock ( &port_startup );	/* serialize access */
    /*
     * Make dummy client context so callback can use this module's read
     * and write functions.
     */
    mgr.port_num = mgr.index = mgr.transaction_count = 0;
    mgr.stack_top = (int *) remote;	/* structure  on caller's stack */
    mgr.time_limit=0; 
    mgr.scavenge_eligible = 0;
    mgr.preserve=1;
    mgr.chan = client_chan;
    mgr.local_ip_addr=ctx->local_ip_addr;
    mgr.remote_address = remote->a;
    if ( !mgr_initted ) {
	/*
	 * One-time setup.
	 */
	mgr_initted = 1;
        status = INITIALIZE_CONDITION ( &mgr.io_done );
	SET_COND_NAME(&mgr.io_done,"OSU TCP mgr io_done")
    }
    pthread_setspecific ( client_key, &mgr );
    /*
     * Since processing is done by listen thread, put time limit (5 sec) on 
     * the length of the operation.  Don't use time limit if multiple listen
     * threads.
     */
    if ( local_addr_count < 2 ) ts_tcp_set_time_limit ( &mgr, 5 );
    /*
     * Have manage callback process the request.
     */
    status = (*ts_manage_callback) ( &mgr, ctx->port_num, &shutdown_time );
    pthread_setspecific ( client_key, (void *) 0 );
    /*
     * Any non-zero status returned by callback causes shutdown.
     */
    if ( status ) {
	/*
	 * Wait indicated time for all pending action to run down.
	 */
	ts_tcp_set_time_limit ( &mgr, 0 );	/* cancel timeout */
	delta.tv_sec = shutdown_time;
	delta.tv_nsec = 0;
	if ( 0 != pthread_get_expiration_np(&delta, &abstime) ) {
	    ts_tcp_write ( &mgr, "Error setting timer, abort\n", 27 );
	    ts_tcp_close ( &mgr );
    	    pthread_mutex_unlock ( &port_startup );	/* serialize access */
	    return status;
	}
	/*
	 * Abort scavengable threads and wait for existing clients in pool to 
         * rundown.
	 */
	pthread_mutex_lock ( &tcp_ctl );
	PUTLOG ( 1, "shutting down TCP listenport !SL!/", ctx->port_num );
	pool = ctx->pool;
	reclaim_scavenge_list ( pool, 1000000000 );
	count = pool->client_count;		/* initial active */
	while ( pool->client_count > 0 ) {
	    pool->connect_pending++;
	    if ( 0 != pthread_cond_timedwait 
		    ( &pool->client_available, &tcp_ctl, &abstime ) ) break;
	}
	pool->connect_pending = 0;
	pthread_mutex_unlock ( &tcp_ctl );
	/*
	 * Write back number we randown and close connection.
	 */
	tu_strint ( count, number );
	ts_tcp_write ( &mgr, number, tu_strlen(number) );
	ts_tcp_close ( &mgr );
    }
    pthread_mutex_unlock ( &port_startup );
    /* pthread_cond_destroy ( &mgr.io_done ); */
    return status;
}
/***************************************************************************/
/* Issue QIO to accept connection.
 */
static int accept_connect ( int port_num, int listen_chan, int client_chan,
	pthread_cond_t *io_cond, socket_info *remote )
{
    int status;
    struct { short status, count, d1, d2; } iosb;
#ifdef TWGTCP
	status = QIO_AND_WAIT ( io_cond, client_chan, IO$_ACCEPT, &iosb,
		remote, sizeof(socket_info), listen_chan, 0, 0, 0 );
#endif
#ifdef UCXTCP
	struct { long length; struct SOCKADDRIN *va;
		int *retadr; long list_end; } sockname;
	sockname.length = sizeof(struct SOCKADDRIN);
	sockname.va = &remote->a;
	sockname.retadr = &remote->length;
	sockname.list_end = 0;
	remote->a.SIN$W_FAMILY = UCX$C_AF_INET;
	remote->a.SIN$W_PORT = 0;	/* any port */
	remote->a.SIN$L_ADDR = 0;
        memset(remote->a.SIN$T_ZERO,0,sizeof(remote->a.SIN$T_ZERO));

	status = QIO_AND_WAIT ( io_cond, listen_chan, IO$_ACCESS|IO$M_ACCEPT, 
		&iosb,	0, 0, &sockname, &client_chan, 0, 0 );

	if ( (status&1) == 0 ) {
	    /* Hack for diagnosing UCX 5.1 bug */
	    remote->a.SIN$W_FAMILY = iosb.count;
	    remote->a.SIN$W_PORT = iosb.d1;
	}
#endif
#ifdef MULTINET
	status = QIO_AND_WAIT ( io_cond, client_chan, IO$_ACCEPT, &iosb,
		remote, sizeof(socket_info), listen_chan, 0, 0, 0 );
#endif
#ifdef TCPWARE
	status = QIO_AND_WAIT ( io_cond, client_chan, IO$_ACCEPT, &iosb,
		remote, sizeof(socket_info), listen_chan, 0, 0, 0 );
#endif
#ifdef CMUTCP
    struct cnx_info {
	unsigned short foreign_size, local_size;		/* name sizes */
	char foreign_host[128];
	unsigned long foreign_port;			/* Only low bytesused*/
	char local_host[128];				/* host names */
	unsigned long local_port;
	unsigned long local_addr, foreign_addr;		/* IP addresses */
    } connect_info;
	
#define CMU_CTO 0x86380e2
	do {
	    status = QIO_AND_WAIT ( io_cond, client_chan, IO$_CREATE, &iosb,
		CMU_LISTEN_ADDRESS, 0, port_num,
		CMU_LISTEN_FLAGS, 0, 0);
	    if ( (status&0x0FFFFFFF) == CMU_CTO ) {
		PUTLOG(0,
		   "Connection timeout in accept call, port !SL at !%D!/",
		   port_num, 0 );
		status = CMU_CTO;
	    }
	} while ( status == CMU_CTO );
	if ( (status&1) == 0 ) return status;
	/*
	 * Get address info on this connection and build sockaddr structure.
	 */
	status = QIO_AND_WAIT ( io_cond, client_chan, IO$_MODIFY, &iosb,
		&connect_info, sizeof(connect_info), 0, 0, 0, 0 );
	if ( (status&1) == 1 ) {
	    remote->a.sin_family = PF_INET;
	    remote->a.sin_port = HTONS(connect_info.foreign_port);
	    remote->a.sin_addr.s_addr = connect_info.foreign_addr;
	} else {
	    /* Assume connection broken and fake the info.  Further
	     * reads should fail, but that will be handled.
	     */
	    status = 1;
	    remote->a.sin_family = PF_INET;
	    remote->a.sin_port = 0;
	    remote->a.sin_addr.s_addr = 0;
	}
#endif
    return status;
}
/***************************************************************************/
/* Do interface-specific operations neccessary to set TCP channel for
 * recieving connections on specified port number.
 */
static int create_listen_socket ( int port_num,  
	unsigned long local_ip_addr, short chan, pthread_cond_t *cond )
{
    int status, opt_val, i;
    struct { short status, count; long ddep; } iosb;
#ifdef TWGTCP
    struct sockaddr_in sock;

    status = QIO_AND_WAIT ( cond, chan, IO$_SOCKET, &iosb,
	PF_INET, SOCK_STREAM, 0, 0, 0, 0 );
    if ( (status&1) == 0 ) return status;

    /* Set socket option on socket to allow quick reuse of address */
    opt_val = 1;
    status = QIO_AND_WAIT ( cond, chan, IO$_SETSOCKOPT, &iosb,
		SOL_SOCKET, SO_REUSEADDR, &opt_val, sizeof(opt_val), 0, 0 );

    /* Set socket option to verify connection periodically */
    opt_val = 1;
    status = QIO_AND_WAIT ( cond, chan, IO$_SETSOCKOPT, &iosb,
		SOL_SOCKET, SO_KEEPALIVE, &opt_val, sizeof(opt_val), 0, 0 );
    if ( (status&1) == 0 ) return status;

    /* Bind socket to port number */
    sock.sin_family = PF_INET;
    sock.sin_port = HTONS(port_num);
    sock.sin_addr.s_addr = local_ip_addr;
    for(i=0;i<sizeof(sock.sin_zero);i++) sock.sin_zero[i] = 0;

    status = QIO_AND_WAIT ( cond, chan, IO$_BIND, &iosb,
		&sock, sizeof(sock), 0, 0, 0, 0 );
    if ( (status&1) == 0 ) return status;
    /*
     * Set backlog for connect requests.
     */
    status = QIO_AND_WAIT ( cond, chan, IO$_LISTEN, &iosb,
		ts_listen_backlog, 0, 0, 0, 0, 0 );
    if ( (status&1) == 0 ) return status;
#endif
#ifdef UCXTCP
    int flag;
    struct sockname { long length; int *va; int *retadr; long list_end;
	struct SOCKADDRIN a; };
    struct sockname local;
    struct sockchar { short protocol; char ptype, domain; } sockchar;
    struct  itlst { short length, code;
	  union { struct itlst *lst; int *val; } addr; } olst[3];

    sockchar.protocol = 6; /* UCX$C_TCP; */
    sockchar.ptype =  1; /* UCX$C_STREAM; */
    sockchar.domain = 2; /* UCX$C_AF_INET; */

    local.length = sizeof(struct SOCKADDRIN);
    local.va = (int *) &local.a;
    local.retadr = 0;
    local.list_end = 0;
    local.a.SIN$W_FAMILY = UCX$C_AF_INET;
    local.a.SIN$W_PORT = HTONS(port_num);
    local.a.SIN$L_ADDR = local_ip_addr;
    memset(local.a.SIN$T_ZERO,0,sizeof(local.a.SIN$T_ZERO));
    /*
     * Create socket with parameters we want.
     */
    olst[0].code = UCX$C_SOCKOPT; olst[0].length = 2*sizeof(struct itlst);
    olst[0].addr.lst = &olst[1];
    olst[1].length = 4; olst[1].code = UCX$C_REUSEADDR;
    olst[1].addr.val = &flag; flag = 1;
    olst[2].length = 4; olst[2].code = UCX$C_KEEPALIVE;
    olst[2].addr.val = &flag;

    status = QIO_AND_WAIT ( cond, chan, IO$_SETMODE, &iosb,
		&sockchar, 0, 0, 0, 0, 0 );
    if ( (status&1) == 0 ) return status;

    status = QIO_AND_WAIT ( cond, chan, IO$_SETMODE, &iosb,
		0, 0, 0, 0, &olst, 0 );
    if ( (status&1) == 0 ) return status;
    /*
     * Bind socket.
     */
    status = QIO_AND_WAIT ( cond, chan, IO$_SETMODE, &iosb,
		0, 0, &local, ts_listen_backlog, 0, 0 );
    if ( (status&1) == 0 ) return status;
#endif    
#ifdef MULTINET
    struct sockaddr_in sock;

    status = QIO_AND_WAIT ( cond, chan, IO$_SOCKET, &iosb,
		AF_INET, SOCK_STREAM, 0, 0, 0, 0 );
    if ( (status&1) == 0 ) return status;

    /* Set socket option on socket to allow quick reuse of address */
    opt_val = 1;
    status = QIO_AND_WAIT ( cond, chan, IO$_SETSOCKOPT, &iosb,
		SOL_SOCKET, SO_REUSEADDR, &opt_val, sizeof(opt_val), 0, 0 );

    /* Set socket option to verify connection periodically */
    opt_val = 1;
    status = QIO_AND_WAIT ( cond, chan, IO$_SETSOCKOPT, &iosb,
		SOL_SOCKET, SO_KEEPALIVE, &opt_val, sizeof(opt_val), 0, 0 );
    if ( (status&1) == 0 ) return status;

    /* Bind socket to port number */
    sock.sin_family = AF_INET;
    sock.sin_port = HTONS(port_num);
    sock.sin_addr.s_addr = local_ip_addr;
    for(i=0;i<sizeof(sock.sin_zero);i++) sock.sin_zero[i] = 0;

    status = QIO_AND_WAIT ( cond, chan, IO$_BIND, &iosb,
		&sock, sizeof(sock), 0, 0, 0, 0 );
    if ( (status&1) == 0 ) return status;
    /*
     * Set backlog for connect requests.
     */
    status = QIO_AND_WAIT ( cond, chan, IO$_LISTEN, &iosb,
		ts_listen_backlog, 0, 0, 0, 0, 0 );
    if ( (status&1) == 0 ) return status;
#endif
#ifdef TCPWARE
    struct sockaddr_in sock;

    status = QIO_AND_WAIT ( cond, chan, IO$_SOCKET, &iosb,
		AF_INET, SOCK_STREAM, 0, 0, 0, 0 );
    if ( (status&1) == 0 ) return status;

    /* Set socket option on socket to allow quick reuse of address */
    opt_val = 1;
    status = QIO_AND_WAIT ( cond, chan, IO$_SETSOCKOPT, &iosb,
		SOL_SOCKET, SO_REUSEADDR, &opt_val, sizeof(opt_val), 0, 0 );

    /* Set socket option to verify connection periodically */
    opt_val = 1;
    status = QIO_AND_WAIT ( cond, chan, IO$_SETSOCKOPT, &iosb,
		SOL_SOCKET, SO_KEEPALIVE, &opt_val, sizeof(opt_val), 0, 0 );
    if ( (status&1) == 0 ) return status;

    /* Bind socket to port number */
    sock.sin_family = AF_INET;
    sock.sin_port = HTONS(port_num);
    sock.sin_addr.s_addr = local_ip_addr;
    for(i=0;i<sizeof(sock.sin_zero);i++) sock.sin_zero[i] = 0;

    status = QIO_AND_WAIT ( cond, chan, IO$_BIND, &iosb,
		&sock, sizeof(sock), 0, 0, 0, 0 );
    if ( (status&1) == 0 ) return status;
    /*
     * Set backlog for connect requests.
     */
    status = QIO_AND_WAIT ( cond, chan, IO$_LISTEN, &iosb,
		ts_listen_backlog, 0, 0, 0, 0, 0 );
    if ( (status&1) == 0 ) return status;
#endif
#ifdef CMUTCP
    status = SYS$DASSGN ( chan );
#endif
    return status;
}
/***************************************************************************/
/* Shell calling cient start routine with extra parameters.  Save initial
 * stack pointer so we can computer some idea of usage.
 */
static int ts_startup_client ( client_ctx ctx )
{
    auto int auto_var;
    int status;
    pthread_setspecific ( client_key, ctx );
    SET_THREAD_NAME(pthread_self(),ctx->thread_name)
    ctx->stack_top = &auto_var;
    ctx->status = 1;			/* in progress */
    status = (*ctx->start)(ctx, ctx->port_num, 
		(unsigned char *) &ctx->remote_address, 
		ctx->index, ctx->clients_remaining );
    return status;
}
/***************************************************************************/
int ts_tcp_stack_used ()
{
    char auto_var;
    client_ctx ctx;

    GET_SPECIFIC ( client_key, ctx )
    if ( !ctx ) return 0;
    return ((int)ctx->stack_top - (int)&auto_var);
}
/***************************************************************************/
/* Return TCP information, 3 variants:
 *    Local_port, remote_port, remote_address;
 *    local_port, local_interface	(specify remote_port null)
 *    TCP_IP stack code			(call outside connext + null local_port)
 */
int ts_tcp_info ( int *local_port, int *remote_port, 
	unsigned int *remote_address )
{
    int status;
    client_ctx ctx;
    GET_SPECIFIC ( client_key, ctx );
    if ( ctx == (client_ctx) 0 ) {
	/*
	 * When called outside of a connection context with no local_port, 
	 * return a code number for the TCP option.
	 */
	if ( local_port ) return -1;
	return TCPSEL(1,2,3,4,5);	/* ucx, multinet, twg, cmu, tcpware) */
    }

    *local_port = ctx->port_num;
    if ( remote_port ) {
        *remote_port = ctx->remote_address.TCPSEL(SIN$W_PORT,sin_port,sin_port,
		sin_port, sin_port);
        *remote_port = HTONS(*remote_port);
        *remote_address = ctx->remote_address.TCPSEL(SIN$L_ADDR,sin_addr.s_addr,
		sin_addr.s_addr,sin_addr.s_addr,sin_addr.s_addr);
    } else {
	/*
	 * Hack for multi-homed operation, return local ip address.
	 */
	*remote_address = (unsigned int) ctx->local_ip_addr;
    }
    return 0;
}
/***************************************************************************/
/* Define routine to return host name for current connection.  The return
 * value is a pointer to a statically allocated area that holds the host name
 * or a formatted representation of the host address.
 */
char *ts_tcp_remote_host ( )
{
    int length, status, i, octet, slot;
    unsigned long remote_address;
    unsigned char *octet_p;
    char *cp;
    client_ctx ctx;
    /*
     * Locate the context block for this thread and validate it.
     */
    GET_SPECIFIC ( client_key, ctx );
    if ( ctx == (client_ctx) 0 ) return (char *) 0;
    /*
     * Check status of host name and return it if already retrieved.
     */
    if ( ctx->hn_status ) return &ctx->hostname[0];
    /*
     * See if address is in local cache.
     */
    remote_address = (unsigned long) ctx->remote_address.TCPSEL(SIN$L_ADDR,
		sin_addr.s_addr,sin_addr.s_addr,sin_addr.s_addr,sin_addr.s_addr);
    slot = ((remote_address>>24)^((remote_address>>16)&255)) & (CB_SIZE-1);
    pthread_mutex_lock ( &tcp_ctl );		/* Lock while examining cache*/
    if ( (cb[slot].status == 1) && (cb[slot].address == remote_address)) {
	    ctx->hn_status = 1;
	    tu_strcpy ( ctx->hostname, cb[slot].hostname );
    }
    pthread_mutex_unlock ( &tcp_ctl );
    /*
     * Check status again after cache check.
     */
    if ( ctx->hn_status ) return ctx->hostname;

    else {
        /*
         * Host name unknown, do lookup specific to TCP driver in use.  
         * Hostname info is stored in client context block using the 
         * following fields:
         *
         *    ctx->dns_chan	Package specific use (short int), initialized
         *			by port_startup() to -1.
         *    ctx->hn_status	Status of ctx->hostname[] array:
         *			   0 Initial state, hostname invalid.
         *			   1 Known, hostname[] valid.
         *			  -1 Unknown, previous lookup attempt failed.
         *			     (buffer valid, formatted string).
         *
         *    ctx->hostname[]	Host name, zero-terminated string.
         */
#ifdef MULTINET
	struct hostent *hostinfo;
	/*
	 * Assume for now that Multinet library is not re-entrant.
	 */
	pthread_lock_global_np();
        hostinfo = 
	    gethostbysockaddr ( &ctx->remote_address,sizeof(ctx->remote_address));
        if ( hostinfo ) {
	    tu_strnzcpy ( ctx->hostname, hostinfo->h_name ?
		hostinfo->h_name : "<error>", sizeof(ctx->hostname)-1 );
	    ctx->hn_status = 1;
        }
	pthread_unlock_global_np();
#endif
#ifdef TCPWARE
	struct hostent *hostinfo;
	/*
	 * Assume for now that Multinet library is not re-entrant.
	 */
	pthread_lock_global_np();
        hostinfo = 
	    gethostbyaddr ( &ctx->remote_address.sin_addr.s_addr,
		sizeof(ctx->remote_address.sin_addr.s_addr),
		ctx->remote_address.sin_family );
        if ( hostinfo ) {
	    tu_strnzcpy ( ctx->hostname, hostinfo->h_name ?
		hostinfo->h_name : "<error>", sizeof(ctx->hostname)-1 );
	    ctx->hn_status = 1;
        }
	pthread_unlock_global_np();
#endif
#ifdef TWGTCP
	struct hostent *hostinfo;
	/*
	 * Assume for now that Multinet library is not re-entrant.
	 */
	pthread_lock_global_np();
        hostinfo = 
	    gethostbyaddr ( &ctx->remote_address.sin_addr.s_addr, 
		sizeof(ctx->remote_address.sin_addr.s_addr),
		ctx->remote_address.sin_family );
        if ( hostinfo ) {
	    tu_strnzcpy ( ctx->hostname, hostinfo->h_name ?
		hostinfo->h_name : "<error>", sizeof(ctx->hostname)-1 );
	    ctx->hn_status = 1;
        }
	pthread_unlock_global_np();
#endif
#ifdef UCXTCP
	struct {
	    long length; unsigned char *va;		/* descriptor */
	    unsigned char func, subfunc, dum1, dum2;
	} p1, p2, p4;
        /*
         * setup arguments for ACPCONTROL function.
         */
	length = 0;
	p1.length = 4; p1.va = &p1.func;
	p1.func = 2;		/* gethostbyaddr */
	p1.subfunc = 2;		/* trans */
	p2.length = sizeof(ctx->remote_address.SIN$L_ADDR);
	p2.va = (unsigned char *) &ctx->remote_address.SIN$L_ADDR;
	p4.length = sizeof(ctx->hostname)-1;
	p4.va = (unsigned char *) ctx->hostname;
	ctx->dns_chan = ctx->chan;	

	status = QIO_AND_WAIT ( &ctx->io_done, ctx->dns_chan, IO$_ACPCONTROL, 
		&ctx->iosb, &p1, &p2, &length, &p4, 0, 0 );
	if ( (status&1) == 1 ) {
	    /*
	     * Lookup successful, terminate string and change status.
	     */
	    ctx->hn_status = 1;
	    ctx->hostname[length] = '\0';
	}
#endif
#ifdef CMUTCP
	struct { long length; char hostname[256]; } host_buf;

	ctx->dns_chan = ctx->chan;
	status = QIO_AND_WAIT ( &ctx->io_done, ctx->dns_chan, IO$_SKIPFILE, 
		&ctx->iosb,
		&host_buf, sizeof(host_buf), 2, remote_address, 0, 0 );
	if ( (status&1) == 1 ) {
	    /*
	     * Lookup successful, copy result and change status.
	     */
	    ctx->hn_status = 1;
	    if ( host_buf.length > (sizeof(ctx->hostname)-1) )
		host_buf.length = sizeof(ctx->hostname)-1;
	    tu_strnzcpy ( ctx->hostname, host_buf.hostname, host_buf.length );
	}
#endif
    }
    /*
     * If name lookup worked, place in cache.
     */
    if ( ctx->hn_status == 1 ) {
	pthread_mutex_lock ( &tcp_ctl );
	cb[slot].address = remote_address;
	cb[slot].status = 1;
	tu_strcpy ( cb[slot].hostname, ctx->hostname );
	pthread_mutex_unlock ( &tcp_ctl );

    } else {
        /*
         * Unable to determine host name, format it as n.n.n.n, using threadsafe
         * formatting routine (tu_strint).
         */
        ctx->hn_status = -1;
        octet_p = (unsigned char *) &remote_address;
        cp = ctx->hostname;
        for ( i = 0; i < 4; i++ ) {
	    octet = *octet_p++;		/* Convert to int */
	    tu_strint ( octet, cp ); while ( *cp ) cp++;
	    if ( i < 3 ) *cp++ = '.';	/* Delimit each label */
        }
    }
    return ctx->hostname;
}
/***************************************************************************/

static void client_rundown ( client_ctx ctx )
{
    int status, SYS$DASSGN();
    client_ctx t;
    port_ctx parent;
    struct client_pool *pool;
    if ( !ctx ) return;

    /* printf("running down tcp client, index=%d\n", ctx->index ); */
    ctx->status = 2;	/* flag as deleted */
    if ( !ctx->preserve ) {
	status = SYS$DASSGN ( ctx->chan ); /* Potential race? */
    }
    DETACH_THREAD ( ctx->thread );

    pthread_mutex_lock ( &tcp_ctl );
    parent = ctx->parent;
    pool = parent->pool;
    /* Decrement counts */
    --parent->client_count;
    t = (client_ctx) ctx->flink;
    t->blink = ctx->blink;
    t = (client_ctx) ctx->blink; t->flink = ctx->flink;

    if ( parent->delete_pending ) {
	PUTLOG ( 0, "Client rundown on deleted port (!SL)!/", 
		ctx->port_num );
	if ( parent->client_count <= 0 ) {	/* we can now delete parent */
	    LOCK_C_RTL
    	    free ( parent );
	    UNLOCK_C_RTL
	}
    }
    /* Place on free list for pool, signal anybody waiting for client. */
    --pool->client_count;
    ctx->flink = (port_ctx) pool->free_clients;
    pool->free_clients = ctx;
    if ( pool->connect_pending ) {
	    PUTLOG ( 0, "Signalling client available: !%T!/", 0 );
	    pthread_cond_signal ( &pool->client_available );
    }
    if ( pool->ref_count + pool->client_count <= 0 ) {
      /*
       * Nobody left to use pool, deallocate it.
       */
      LOCK_C_RTL
      for ( t=pool->free_clients; t; t = pool->free_clients ) {
	pool->free_clients = (client_ctx) t->flink;
	if ( t->index <= pool->cond_highwater )
		pthread_cond_destroy ( &t->io_done );
	free ( t );
      }
      pthread_cond_destroy ( &pool->client_available );
      free ( pool );
      /* printf("Deleted client pool with final client\n"); */
      UNLOCK_C_RTL
    }

    pthread_mutex_unlock ( &tcp_ctl );
}

static void port_rundown (port_ctx ctx )
{
    port_ctx t;
    client_ctx client;
    struct client_pool *pool;
    int count, ref_count, active_clients;

    LOCK_C_RTL
    printf("running down tcp listen port, ctx address: %d\n", ctx );
    UNLOCK_C_RTL
    if ( !ctx ) return;
    if ( ctx->status ) {
	/* 
	 * Pre-mature rundown during port startup, continue initiating thread
	 */
	pthread_mutex_lock ( &port_startup );
	startup_status = ctx->status;
        ctx->status = 0;
	pthread_mutex_unlock ( &port_startup );
	pthread_cond_signal ( &port_startup_done );
    }

    /* Rundown clients */
    pthread_mutex_lock ( &tcp_ctl );
    for ( client = (client_ctx) ctx->client_list.flink;
	   client != &ctx->client_list; client = (client_ctx) client->flink ) {
	pthread_cancel ( client->thread );
    }
    count = ctx->client_count;
    pthread_mutex_unlock ( &tcp_ctl );
    /*
     * Remove context struct from port list and dereference from pool.
     */
    pthread_mutex_lock ( &tcp_ctl );
    ctx->delete_pending = 1;
    t = ctx->flink; t->blink = ctx->blink;
    t = ctx->blink; t->flink = ctx->flink;
    pool = ctx->pool;
    --pool->ref_count;
    ref_count = pool->ref_count + pool->client_count;
    /*
     * Release resources.
     */
#ifndef CMUTCP
    SYS$DASSGN ( ctx->chan );
#endif
    if ( ctx->shutdown_monitor ) DETACH_THREAD ( ctx->thread );
    LOCK_C_RTL
    if ( ctx->client_count <= 0 ) free ( ctx );
    UNLOCK_C_RTL
    pthread_mutex_unlock ( &tcp_ctl );

   if ( ref_count == 0 ) {
      /*
       * Nobody left to use pool, deallocate it.
       */
      LOCK_C_RTL
      for ( client=pool->free_clients; client; client = pool->free_clients ) {
	pool->free_clients = (client_ctx) client->flink;
	if ( client->index <= pool->cond_highwater )
		pthread_cond_destroy ( &client->io_done );
	free ( client );
      }
      pthread_cond_destroy ( &pool->client_available );
      free ( pool );
      /* printf("Deleted client pool with port\n"); */
      UNLOCK_C_RTL
    }
}
/*************************************************************************/
int ts_tcp_transaction_count ( client_ctx ctx )
{
    return ctx->transaction_count;
}
int ts_tcp_end_transaction ( client_ctx ctx )
{
    int status;
    client_ctx t;
    port_ctx parent;
    struct client_pool *pool;
    client_ctx new_client;
    /*
     * allocate a new client block to same parent context.
     */
    parent = ctx->parent;
    new_client = allocate_client ( parent->pool, parent, 0 );
    if ( !new_client ) return -1;	/* allocation failure */
    /*
     * clone the TCP information.
     */
    new_client->transaction_count = ctx->transaction_count+1;
    new_client->time_limit = 0;
    new_client->preserve = 0;
    new_client->chan = ctx->chan;
    new_client->start = ctx->start;
    new_client->local_ip_addr = ctx->local_ip_addr;
    new_client->remote_address = ctx->remote_address;
    new_client->hn_status = ctx->hn_status;
    if ( ctx->hn_status ) tu_strcpy ( new_client->hostname, ctx->hostname );
    /*
     * create a new thread.
     */
#ifdef PTHREAD_USE_D4
    status = pthread_create ( &new_client->thread, 
		*(parent->pool->client_attr), 
		(pthread_startroutine_t) ts_startup_client, new_client );
#else
    status = pthread_create ( &new_client->thread, parent->pool->client_attr, 
		(startroutine_t) ts_startup_client, new_client );
#endif
    if ( status != 0 ) {
	perror ( "error creating client thread" );
	return status;
    }
    /*
     * Preseve the channel and initiate rundown of current thread.
     */
    ctx->preserve = 1;
    ctx->chan = -1;
    return 0;		/* Normal, successful completion */
}
#endif	/* !BSDTCP */
