/*************************************************************************\
* Copyright (c) 2002 The University of Chicago, as Operator of Argonne
*     National Laboratory.
* Copyright (c) 2002 The Regents of the University of California, as
*     Operator of Los Alamos National Laboratory.
* EPICS BASE Versions 3.13.7
* and higher are distributed subject to a Software License Agreement found
* in file LICENSE that is included with this distribution. 
\*************************************************************************/
/* $Id$	*/
/************************************************************************/
/*									*/
/*	        	      L O S  A L A M O S			*/
/*		        Los Alamos National Laboratory			*/
/*		         Los Alamos, New Mexico 87545			*/
/*									*/
/*	Copyright, 1986, The Regents of the University of California.	*/
/*									*/
/*	Author: Jeffrey O. Hill						*/
/*									*/
/*	History								*/
/*	-------								*/
/*									*/
/*	Date	Person	Comments					*/
/*	----	------	--------					*/
/*	8/87	joh	Init Release					*/
/*	1/90	joh	switched order in task exit			*/
/*			so events are deleted first then closed		*/
/*	3/90	joh	Changed error message strings			*/
/*			to allocate in this module only			*/
/*			& further improved vx task exit			*/
/*	021291 	joh 	fixed potential problem with strncpy and 	*/
/*			the vxWorks task name.				*/
/*	022291 	joh	read sync not incremented when conn is down	*/
/*	030791	joh	vx lcl event buf free prior to first alloc fix	*/
/*	030791	joh	made lcl buffer safe in ca_event_handler()	*/
/*	031291	joh	added DBR_LONG to ca_put, made UNIX and vxWorks	*/
/*			ca_put less sensitive to future data type 	*/
/*			additions, added better string bounds checking	*/
/*	060591	joh	delinting					*/
/*	061391	joh	RISC alignment in outgoing messages		*/
/*	070191	joh	always use memcpy in ca_put			*/
/*	071291	joh	added CLAIM_CIU message				*/
/*	072391	joh	added event locking for vxWorks			*/
/*	072591	joh	quick POLL in ca_pend_io() should return 	*/
/*			ECA_NORMAL not ECA_TIMEOUT if pend count == 0	*/
/*	082391	joh	check for connection down when reissuing	*/
/*			monitors					*/
/*	090991	joh	converted to v5 vxWorks				*/
/*	092691	joh	check for connection down when inserting msg	*/
/*	111891	joh	better test for ca_pend during an event routine	*/
/*			under vxWorks					*/
/*	111991	joh	dont decr the pend recv count if connected 	*/
/*			before and they are adding a connection handler */
/*			prior to connecting				*/
/* 	111991	joh	selective activation of LOCK with CLRPENDRECV	*/
/*			prevents double LOCK under vxWorks		*/
/*	020392	joh	added calls to taskVarGetPatch to bypass	*/
/*			v5 vxWorks bug.					*/
/*	021392 	joh	dont zero ca_static in the exit handler under	*/
/*			vxWorks since vxWorks may run the exit handler	*/
/*			in the context of another task which has a	*/
/*			valid ca_static of its own.			*/
/*	022692	joh	Use channel state enum to determine if I need	*/
/*			to send channel clear message to the IOC 	*/
/*			instead of the IOC in use conn up flag		*/
/*	031292 	joh	clear pend recv cnt even if its a poll in	*/
/*			ca_pend_io()					*/
/*	031892 	joh	initial broadcast retry delay is now a #define	*/
/*	032092	joh	dont allow them to add an event which does not	*/
/*			select anything (has a nill mask)		*/
/*	041492	joh	Use channel state enum to determine if I need	*/
/*			to send monitor clear message to the IOC 	*/
/*			instead of the IOC in use conn up flag		*/
/*			(did the same thing to the tests in 		*/
/*			issue_get_callback() & request_event())		*/
/*	041492	joh	fixed bug introduced by 022692 when the chan	*/
/*			state enum was used after it was set to		*/
/*			cs_closed					*/
/*	042892	joh	no longer checks the status from free() as	*/
/*			this varies from OS to OS			*/
/*	050492	joh	batch up flow control messages by setting a	*/
/*			send_needed flag				*/
/*	060392	joh	added ca_host_name()				*/
/*	072792	joh	better messages					*/
/*	072792	joh	wrote ca_test_io()				*/
/*	102992	joh	I notice that the vxWorks FP task option cant 	*/
/*			be modified after task creation so CA now wont	*/
/*			init if the task does not have the FP opt set	*/ 
/*	111892	joh	handle the case where no broadcast interface	*/
/*			is available better				*/
/*	111992	joh	added new arg to db_start_events() call		*/
/*	120992	joh	switched to dll list routines			*/
/*	122192	joh	increment outstanding ack count			*/
/*	050593	joh	dont enable deadlock prevention if we are in	*/
/*			post message					*/
/*	070293	joh	set ca_static to nill at the end of		*/
/*			ca_process_exit() under all os and not just	*/
/*			vxWorks						*/
/*	120293	joh	flush in ca_pend_io() if no IO oustanding	*/
/*	121693	joh	fixed bucketLib level memory leak		*/
/*	011394	joh	fixed bucketLib level memory leak (vxWorks)	*/
/*	020494	joh	Added user name protocol			*/
/*      022294  joh     fixed recv task id verify at exit (vxWorks)     */
/*      072895  joh     fixed problem resulting from unsigned long 	*/
/*			tv_sec var in struct timeval in sys/time.h	*/
/*			under HPUX					*/
/************************************************************************/
/*
 * $Log$
 * Revision 1.107.2.10  2002/05/28 18:41:41  jhill
 * fixed ellFree cause malloc and free to be in different win32 dlls
 *
 * Revision 1.107.2.9  2002/03/14 01:13:57  jhill
 * fixed put notify shutdown during channel delete
 *
 * Revision 1.107.2.8  2002/03/08 00:25:08  jhill
 * fixed bug where the db_put_field in the local event callback stub was supplied
 * with a destination that was the database field. In situations where the dbAccess
 * db_access field types were different the database was damaged.
 *
 * Revision 1.107.2.7  2001/09/18 12:17:48  mrk
 * increased stack size for cac_recv_task from 4096 to 8192
 *
 * Revision 1.107.2.6  2000/11/30 22:07:50  jhill
 * changes from Ken Evans
 *
 * Revision 1.107.2.5  2000/08/29 20:03:04  jhill
 * fixed alignment bug check on hosts that have 64 bit longs
 *
 * Revision 1.107.2.4  2000/06/28 15:48:43  mrk
 * remove blank from task name
 *
 * Revision 1.107.2.3  2000/06/28 15:41:15  jhill
 * changed "m_type" to "m_dataType" in order to avoid collision
 * with MACRO supplied by WRS's Tornado II product
 *
 * Revision 1.107.2.2  1999/09/02 21:16:40  jhill
 * fixed missing paramter to checkConnWatchDogs() func
 *
 * Revision 1.107.2.1  1999/07/15 20:23:57  jhill
 * fixed bug where client disconnects while waiting to send TCP
 *
 * Revision 1.107  1998/10/27 00:43:27  jhill
 * eliminated warning
 *
 * Revision 1.106  1998/09/25 00:20:56  jhill
 * fixed warnings
 *
 * Revision 1.105  1998/09/24 21:10:38  jhill
 * o test routine added
 * o allow large PV names
 *
 * Revision 1.104  1998/06/16 00:24:26  jhill
 * avoid purify warning
 *
 * Revision 1.103  1998/04/23 01:04:05  jhill
 * fixed overzelous chan check in ca_clear_channel() - when the PV is local under vxWorks
 *
 * Revision 1.102  1998/04/13 19:14:33  jhill
 * fixed task variable problem
 *
 * Revision 1.101  1998/03/12 20:39:05  jhill
 * fixed problem where 3.13.beta11 unable to connect to 3.11 with correct native type
 *
 * Revision 1.100  1998/02/05 21:55:49  jhill
 * detect attempts by user to clear bogus channel
 *
 * Revision 1.98  1997/08/04 22:54:28  jhill
 * mutex clean up
 *
 * Revision 1.96  1997/06/13 16:57:38  jhill
 * fixed warning
 *
 * Revision 1.95  1997/06/13 09:14:06  jhill
 * connect/search proto changes
 *
 * Revision 1.94  1997/05/05 04:40:29  jhill
 * send_needed replaced by pushPending flag
 *
 * Revision 1.93  1997/04/29 06:05:57  jhill
 * use free list
 *
 * Revision 1.92  1997/04/23 17:04:57  jhill
 * pc port changes
 *
 * Revision 1.91  1997/04/10 19:26:01  jhill
 * asynch connect, faster connect, ...
 *
 * Revision 1.90  1997/01/22 21:06:30  jhill
 * use genLocalExcepWFL for generateLocalExceptionWithFileAndLine
 *
 * Revision 1.89  1997/01/10 21:02:10  jhill
 * host/user name set is now a NOOP
 *
 * Revision 1.88  1996/11/22 19:05:48  jhill
 * added const to build and connect API
 *
 * Revision 1.87  1996/11/02 00:50:30  jhill
 * many pc port, const in API, and other changes
 *
 * Revision 1.86  1996/09/16 16:41:47  jhill
 * local except => except handler & ca vers str routine
 *
 * Revision 1.85  1996/09/04 20:02:00  jhill
 * test for non-nill piiu under vxWorks
 *
 * Revision 1.84  1996/07/10 23:30:09  jhill
 * fixed GNU warnings
 *
 * Revision 1.83  1996/07/09 22:43:29  jhill
 * silence gcc warnings and default CLOCKS_PER_SEC if it isnt defined (for sunos4 and gcc)
 *
 * Revision 1.82  1996/06/19 17:58:59  jhill
 * many 3.13 beta changes
 *
 * Revision 1.81  1995/12/19  19:28:11  jhill
 * Dont check the array element count when they add the event (just clip it)
 *
 * Revision 1.80  1995/10/18  16:49:23  jhill
 * recv task is now running at a lower priority than the send task under vxWorks
 *
 * Revision 1.79  1995/10/12  01:30:10  jhill
 * new ca_flush_io() mechanism prevents deadlock when they call
 * ca_flush_io() from within an event routine. Also forces early
 * transmission of leading search UDP frames.
 *
 * Revision 1.78  1995/09/29  21:47:33  jhill
 * alignment fix for SPARC IOC client and changes to prevent running of
 * access rights or connection handlers when the connection is lost just
 * after deleting a channel
 *
 * Revision 1.77  1995/09/01  14:31:32  mrk
 * Fixed bug causing memory problem
 *
 * Revision 1.76  1995/08/23  00:34:06  jhill
 * fixed vxWorks specific SPARC data alignment problem
 *
 * Revision 1.75  1995/08/22  00:15:19  jhill
 * Use 1.0/USEC_PER_SEC and not 1.0e-6
 * Check for S_db_Pending when calling dbPutNotify()
 *
 * Revision 1.74  1995/08/22  00:12:07  jhill
 * *** empty log message ***
 *
 * Revision 1.73  1995/08/14  19:26:10  jhill
 * epicsAPI => epicsShareAPI
 *
 * Revision 1.72  1995/08/12  00:23:32  jhill
 * check for res id in use, epicsEntry, dont wait for itsy bitsy delay
 * in ca_pend_event(), better clean up when monitor is deleted and
 * we are disconnected
 *
 */
/*_begin								*/
/************************************************************************/
/*									*/
/*	Title:	IOC high level access routines				*/
/*	File:	access.c						*/
/*									*/
/*									*/
/*	Purpose								*/
/*	-------								*/
/*									*/
/*	ioc high level access routines					*/
/*									*/
/*									*/
/*	Special comments						*/
/*	------- --------						*/
/*	Things that could be done to improve this code			*/
/*	1)	Allow them to recv channel A when channel B changes	*/
/*									*/
/************************************************************************/
/*_end									*/

static char *sccsId = "@(#) $Id$";

/*
 * allocate error message string array 
 * here so I can use sizeof
 */
#define CA_ERROR_GLBLSOURCE

/*
 * allocate db_access message strings here
 */
#define DB_TEXT_GLBLSOURCE

/*
 * allocate header version strings here
 */
#define CAC_VERSION_GLOBAL

#include 	"iocinf.h"
#include	"sigPipeIgnore.h"

#include 	"freeList.h"

#ifdef vxWorks
#include 	"dbEvent.h"
#endif


/****************************************************************/
/*	Macros for syncing message insertion into send buffer	*/
/****************************************************************/
#define EXTMSGPTR(PIIU)\
 	((caHdr *) &(PIIU)->send.buf[(PIIU)->send.wtix])




/****************************************************************/
/*	Macros for error checking				*/
/****************************************************************/

/*
 *	valid type check filters out disconnected channel
 */

#define CHIXCHK(CHIX) \
{ \
	if( (CHIX)->state != cs_conn || INVALID_DB_REQ((CHIX)->privType) ){ \
		return ECA_DISCONNCHID; \
	} \
}

/* type to detect them using deallocated channel in use block */
#define TYPENOTINUSE (-2)

/* allow them to add monitors to a disconnected channel */
#define LOOSECHIXCHK(CHIX) \
{ \
	if( (CHIX)->privType==TYPENOTINUSE ){ \
		return ECA_BADCHID; \
	} \
}

static caHdr	nullmsg;

/*
 * local functions
 */

LOCAL int cac_alloc_msg_no_flush(
struct ioc_in_use 	*piiu,
unsigned		extsize,
caHdr		**ppMsg
);
LOCAL int	issue_get_callback(
evid 		monix, 
ca_uint16_t 	cmmd
);
#ifdef vxWorks
LOCAL void ca_event_handler(
void		*usrArg,
struct dbAddr	*paddr,
int		hold,
db_field_log	*pfl
);
LOCAL void ca_put_notify_action(PUTNOTIFY *ppn);
#endif
LOCAL void 	ca_pend_io_cleanup();

LOCAL int issue_ca_array_put
(
ca_uint16_t	cmd,
unsigned	id,
chtype		type,
unsigned long	count,
chid		chix,
const void	*pvalue
);
LOCAL void ca_default_exception_handler(struct exception_handler_args args);

LOCAL int cac_push_msg(
struct ioc_in_use 	*piiu,
caHdr			*pmsg,
const void		*pext
);

LOCAL int cac_push_msg_no_block(
struct ioc_in_use 	*piiu,
caHdr			*pmsg,
const void		*pext
);

LOCAL void cac_add_msg (IIU *piiu);

#ifdef CONVERSION_REQUIRED 
LOCAL void *malloc_put_convert(unsigned long size);
LOCAL void free_put_convert(void *pBuf);
#endif

LOCAL miu caIOBlockCreate(void);

LOCAL int check_a_dbr_string(const char *pStr, const unsigned count);


/*
 *
 * 	cac_push_msg()
 *
 */ 
LOCAL int cac_push_msg(
struct ioc_in_use 	*piiu,
caHdr			*pmsg,
const void		*pext
)
{
	int		contig = piiu->sock_proto != IPPROTO_TCP;
	caHdr		msg;
	unsigned 	bytesAvailable;
	unsigned 	actualextsize;
	unsigned 	extsize;
	unsigned	msgsize;
	unsigned 	bytesSent;

	msg = *pmsg;
	actualextsize = pmsg->m_postsize;
	extsize = CA_MESSAGE_ALIGN(pmsg->m_postsize);
	msg.m_postsize = htons((ca_uint16_t)extsize);
	msgsize = extsize+sizeof(msg);


	LOCK;

	/*
	 * 	Force contiguous header and body 
	 *
	 * o	Forces hdr and bdy into the same frame if UDP
	 *
	 * o	Does not allow interleaved messages
	 *	caused by removing recv backlog during 
	 *	send under UNIX.
	 *
	 * o	Does not allow for messages larger than the
	 *	ring buffer size.
	 */
	if (msgsize>piiu->send.max_msg) {
        UNLOCK;
		return ECA_TOLARGE;
	}

	bytesAvailable = cacRingBufferWriteSize(&piiu->send, contig);

	if (bytesAvailable<msgsize) {
		/*
		 * try to send first so that we avoid the
		 * overhead of select() in high throughput
		 * situations
		 */
		if (piiu->state==iiu_connected) {
			(*piiu->sendBytes)(piiu);
		}

		/*
		 * if connection drops request
		 * cant be completed
		 */
		if (piiu->state!=iiu_connected) {
            UNLOCK;
			return ECA_DISCONNCHID;
		}

		bytesAvailable = 
			cacRingBufferWriteSize(&piiu->send, contig);

		while(TRUE){
			struct timeval	itimeout;

			/*
			 * record the time if we end up blocking so that
			 * we can time out
			 */
			if (bytesAvailable>=msgsize){
				piiu->sendPending = FALSE;
				break;
			}
			else {
				if (!piiu->sendPending) {
					piiu->timeAtSendBlock = 
						ca_static->currentTime;
					piiu->sendPending = TRUE;
				}
			}

			UNLOCK;

			LD_CA_TIME (cac_fetch_poll_period(), &itimeout);
			cac_mux_io (&itimeout, FALSE);

			LOCK;

			/*
			 * if connection drops request
			 * cant be completed
			 */
			if (piiu->state!=iiu_connected) {
                UNLOCK;
				return ECA_DISCONNCHID;
			}

			bytesAvailable = cacRingBufferWriteSize(
						&piiu->send, contig);
		}
	}


	/*
	 * push the header onto the ring 
	 */
	bytesSent = cacRingBufferWrite(
				&piiu->send, 
				&msg, 
				sizeof(msg));
	assert(bytesSent == sizeof(msg));

	/*
	 * push message body onto the ring
	 *
	 * (optionally encode in netrwork format as we send)
	 */
	if (extsize>0u) {
		bytesSent = cacRingBufferWrite(
					&piiu->send, 
					pext, 
					actualextsize);
		assert(bytesSent == actualextsize);
		/*
		 * force pad bytes at the end of the message to nill
		 * if present (this avoids messages from purify)
		 */
		{
			/*
			 * static variables are initialized to zero
			 */
			static char	nullBuff[32];
			unsigned	n;

			n = extsize-actualextsize;
			if (n) {
				assert(n<=sizeof(nullBuff));
				bytesSent = cacRingBufferWrite(
						&piiu->send, 
						nullBuff, 
						n);
				assert(bytesSent == n);
			}
		}
	}

	UNLOCK;

	return ECA_NORMAL;
}


/*
 *
 * 	cac_push_msg_no_block()
 *
 */ 
LOCAL int cac_push_msg_no_block(
struct ioc_in_use 	*piiu,
caHdr			*pmsg,
const void		*pext)
{
	int		contig = piiu->sock_proto != IPPROTO_TCP;
	caHdr		msg;
	unsigned 	bytesAvailable;
	unsigned 	actualextsize;
	unsigned 	extsize;
	unsigned	msgsize;
	unsigned 	bytesSent;

	msg = *pmsg;
	actualextsize = pmsg->m_postsize;
	extsize = CA_MESSAGE_ALIGN(pmsg->m_postsize);
	msg.m_postsize = htons((ca_uint16_t)extsize);
	msgsize = extsize+sizeof(msg);


	LOCK;

	/*
	 * 	Force contiguous header and body 
	 *
	 * o	Forces hdr and bdy into the same frame if UDP
	 *
	 * o	Does not allow interleaved messages
	 *	caused by removing recv backlog during 
	 *	send under UNIX.
	 *
	 * o	Does not allow for messages larger than the
	 *	ring buffer size.
	 */
	if(msgsize>piiu->send.max_msg){
		return ECA_TOLARGE;
	}

	bytesAvailable = cacRingBufferWriteSize(&piiu->send, contig);

	if (bytesAvailable<msgsize) {
		UNLOCK;
		return ECA_SERVBEHIND;
	}

	/*
	 * push the header onto the ring 
	 */
	bytesSent = cacRingBufferWrite(
				&piiu->send, 
				&msg, 
				sizeof(msg));
	assert(bytesSent == sizeof(msg));

	/*
	 * push message body onto the ring
	 *
	 * (optionally encode in netrwork format as we send)
	 */
	if (extsize>0u) {
		bytesSent = cacRingBufferWrite(
					&piiu->send, 
					pext, 
					actualextsize);
		assert(bytesSent == actualextsize);
		/*
		 * force pad bytes at the end of the message to nill
		 * if present (this avoids messages from purify)
		 */
		{
			/*
			 * static variables are initialized to zero
			 */
			static char	nullBuff[32];
			unsigned	n;

			n = extsize-actualextsize;
			if (n) {
				assert(n<=sizeof(nullBuff));
				bytesSent = cacRingBufferWrite(
						&piiu->send, 
						nullBuff, 
						n);
				assert(bytesSent == n);
			}
		}
	}

	UNLOCK;

	return ECA_NORMAL;
}


/*
 *
 * 	cac_alloc_msg_no_flush()
 *
 *	return a pointer to reserved message buffer space or
 *	nill if the message will not fit
 *
 *	dont flush output if the message does not fit
 *
 *	LOCK should be on
 *
 */ 
LOCAL int cac_alloc_msg_no_flush(
struct ioc_in_use 	*piiu,
unsigned		extsize,
caHdr			**ppMsg
)
{
	unsigned 	msgsize;
	unsigned long	bytesAvailable;
	caHdr		*pmsg;

	/*
	 * 	This only works with UDP (because TCP must be allowed
	 * 	to wrap around from the end to the beg of the buffer)
	 */
	assert (piiu->sock_proto == IPPROTO_UDP);

	msgsize = sizeof(caHdr)+extsize;

	/*
	 * fail if max message size exceeded
	 */
	if(msgsize>=piiu->send.max_msg){
		return ECA_TOLARGE;
	}

	bytesAvailable = cacRingBufferWriteSize (&piiu->send, TRUE);
  	if (bytesAvailable<msgsize) {
		return ECA_TOLARGE;
	}

	pmsg = (caHdr *) &piiu->send.buf[piiu->send.wtix];
	pmsg->m_postsize = extsize;
	*ppMsg = pmsg;

	return ECA_NORMAL;
}


/*
 * cac_add_msg ()
 */
LOCAL void cac_add_msg (IIU *piiu)
{
	unsigned long	size; 
	caHdr 	*mp = EXTMSGPTR(piiu); 

	/*
	 * Performs worst case message alignment
	 */
	mp->m_postsize = (unsigned short) 
		CA_MESSAGE_ALIGN(mp->m_postsize); 
	size = mp->m_postsize;
	mp->m_postsize = htons(mp->m_postsize); 
	CAC_RING_BUFFER_WRITE_ADVANCE( 
		&piiu->send, 
		sizeof(caHdr) + size); 
}


/*
 *	ca_os_independent_init ()
 */
int ca_os_independent_init (void)
{
	long		status;

	installSigPipeIgnore();

	ca_static->ca_exception_func = ca_default_exception_handler;
	ca_static->ca_exception_arg = NULL;

	caSetDefaultPrintfHandler();

	/* record a default user name */
	ca_static->ca_pUserName = localUserName();
	if(!ca_static->ca_pUserName){
		return ECA_ALLOCMEM;
	}

	/* record a default user name */
	ca_static->ca_pHostName = localHostName();
	if(!ca_static->ca_pHostName){
		free(ca_static->ca_pUserName);
		return ECA_ALLOCMEM;
	}

	cac_gettimeval (&ca_static->currentTime);
	ca_static->programBeginTime = ca_static->currentTime;

	/* init sync group facility */
	ca_sg_init();

	/*
	 * init broadcasted search counters
	 * (current time must be initialized before calling this)
	 */
    ca_static->ca_search_tries_congest_thresh = UINT_MAX;
	ca_static->ca_search_responses = 0u;
	ca_static->ca_search_tries = 0u;
	ca_static->ca_search_retry_seq_no = 0u;
	ca_static->ca_seq_no_at_list_begin = 0u;
	ca_static->ca_frames_per_try = INITIALTRIESPERFRAME;
	ca_static->ca_conn_next_retry = ca_static->currentTime;
	cacSetRetryInterval (0u);

	ellInit(&ca_static->ca_iiuList);
	ellInit(&ca_static->ca_ioeventlist);
	ellInit(&ca_static->ca_pend_read_list);
	ellInit(&ca_static->ca_pend_write_list);
	ellInit(&ca_static->putCvrtBuf);
	ellInit(&ca_static->fdInfoFreeList);
	ellInit(&ca_static->fdInfoList);

	freeListInitPvt(&ca_static->ca_ioBlockFreeListPVT, 
				sizeof(struct pending_event), 256);

	ca_static->ca_pSlowBucket = 
		bucketCreate(CLIENT_HASH_TBL_SIZE);
	assert(ca_static->ca_pSlowBucket);

	ca_static->ca_pFastBucket = 
		bucketCreate(CLIENT_HASH_TBL_SIZE);
	assert(ca_static->ca_pFastBucket);

	status = envGetDoubleConfigParam (
			&EPICS_CA_CONN_TMO, 
			&ca_static->ca_connectTMO);
	if (status) {
		ca_static->ca_connectTMO = 
			CA_CONN_VERIFY_PERIOD;
		ca_printf (
			"EPICS \"%s\" float fetch failed\n",
			EPICS_CA_CONN_TMO.name);
		ca_printf (
			"Setting \"%s\" = %f\n",
			EPICS_CA_CONN_TMO.name,
			ca_static->ca_connectTMO);
	}

	ca_static->ca_repeater_port = 
		caFetchPortConfig(&EPICS_CA_REPEATER_PORT, CA_REPEATER_PORT);

	ca_static->ca_server_port = 
		caFetchPortConfig(&EPICS_CA_SERVER_PORT, CA_SERVER_PORT);

	if (repeater_installed()==FALSE) {
		ca_spawn_repeater();
	}

	ca_static->ca_flush_pending = FALSE;
	ca_static->ca_min_retry = UINT_MAX;
	ca_static->ca_number_iiu_in_fc = 0u;

	return ECA_NORMAL;
}


/*
 * create_udp_fd
 */
void cac_create_udp_fd()
{
	int	status;

	if(ca_static->ca_piiuCast){
		return;
	}

	status = create_net_chan(
			&ca_static->ca_piiuCast,
			NULL,
			IPPROTO_UDP);
	if (~status & CA_M_SUCCESS) {
		ca_static->ca_piiuCast = NULL;
		return;
	}

#ifdef vxWorks
	{
		int     pri;
		char	name[64];

		status = taskPriorityGet(VXTASKIDSELF, &pri);
		if(status<0)
			genLocalExcep (ECA_INTERNAL,NULL);

		strcpy(name,"RD_");
		strncat(
			name,
			taskName(VXTHISTASKID),
			sizeof(name)-strlen(name)-1);

		status = taskSpawn(
					name,
					pri+1,
					VX_FP_TASK,
					8192,
					(FUNCPTR)cac_recv_task,
					(int)taskIdCurrent,
					0,
					0,
					0,
					0,
					0,
					0,
					0,
					0,
					0);
		if (status==ERROR) {
			genLocalExcep (ECA_INTERNAL,NULL);
		}

		ca_static->recv_tid = status;

	}
#endif
}


/*
 * CA_MODIFY_HOST_NAME()
 *
 * Modify or override the default 
 * client host name.
 *
 * This entry point was changed to a NOOP 
 */
int epicsShareAPI ca_modify_host_name(const char *pHostName)
{
	return ECA_NORMAL;
}



/*
 * CA_MODIFY_USER_NAME()
 *
 * Modify or override the default 
 * client user name.
 *
 * This entry point was changed to a NOOP 
 */
int epicsShareAPI ca_modify_user_name(const char *pClientName)
{
	return ECA_NORMAL;
}



/*
 *
 *	CA_TASK_EXIT_TID() / CA_PROCESS_EXIT()
 *	releases all resources alloc to a channel access client
 *	
 *	On multi thread os it is assumed that all threads are 
 *	before calling this routine.
 *
 */
void ca_process_exit()
{
	chid chix;
	chid chixNext;
	IIU *piiu;
	int status;

	assert (ca_static);

	LOCK;

	/*
	 * after activity eliminated
	 * close all sockets before clearing chid blocks and remote
	 * event blocks
	 */
	piiu = (struct ioc_in_use *)
		ca_static->ca_iiuList.node.next;
	while (piiu) {
		if(ca_static->ca_fd_register_func){
			(*ca_static->ca_fd_register_func)(
				(void *)ca_static->ca_fd_register_arg,
				piiu->sock_chan,
				FALSE);
		}
		if (socket_close(piiu->sock_chan) < 0){
			genLocalExcep ( ECA_INTERNAL, 
				"Problems closing a socket during ca shutdown?");
		}
		piiu = (struct ioc_in_use *) piiu->node.next;
	}

	/*
	 * remove remote chid blocks and event blocks
	 */
	piiu = (struct ioc_in_use *)
		ca_static->ca_iiuList.node.next;
	while(piiu){
		chix = (chid) ellFirst(&piiu->chidlist);
		while (chix) {
			chixNext = (chid) ellNext (&chix->node);
			clearChannelResources (chix->cid);
			chix = chixNext;
		}

		/*
		 * free message body cache
		 */
		if(piiu->pCurData){
			free(piiu->pCurData);
			piiu->pCurData = NULL;
			piiu->curDataMax = 0;
		}

		/*
		 * free address list
		 */
		ellFreeCA(&piiu->destAddr);

		piiu = (struct ioc_in_use *) piiu->node.next;
	}

	/* remove any pending read blocks */
	caIOBlockListFree (&ca_static->ca_pend_read_list, NULL, FALSE, ECA_INTERNAL);

	/* remove any pending write blocks */
	caIOBlockListFree (&ca_static->ca_pend_write_list, NULL, FALSE, ECA_INTERNAL);

	/* remove any pending io event blocks */
	ellFreeCA(&ca_static->ca_ioeventlist);

	/* remove put convert block free list */
	ellFreeCA(&ca_static->putCvrtBuf);

	/* reclaim sync group resources */
	ca_sg_shutdown(ca_static);

	/* remove remote waiting ev blocks */
	freeListCleanup(ca_static->ca_ioBlockFreeListPVT);

	/* free select context lists */
	ellFreeCA(&ca_static->fdInfoFreeList);
	ellFreeCA(&ca_static->fdInfoList);

	/*
	 * remove IOCs in use
	 */
	ellFreeCA(&ca_static->ca_iiuList);

	/*
	 * free user name string
	 */
	if(ca_static->ca_pUserName){
		free(ca_static->ca_pUserName);
	}

	/*
	 * free host name string
	 */
	if(ca_static->ca_pHostName){
		free(ca_static->ca_pHostName);
	}

	/*
	 * free hash tables 
	 */
	status = bucketFree (ca_static->ca_pSlowBucket);
	assert (status == S_bucket_success);
	status = bucketFree (ca_static->ca_pFastBucket);
	assert (status == S_bucket_success);

	/*
	 * free beacon hash table
	 */
	freeBeaconHash(ca_static);

	UNLOCK;
}


/*
 *
 *      CA_BUILD_AND_CONNECT
 *
 *      backwards compatible entry point to ca_search_and_connect()
 */
int epicsShareAPI ca_build_and_connect
(
const char *name_str,
chtype get_type,
unsigned long get_count,
chid * chixptr,
void *pvalue,
void (*conn_func) (struct connection_handler_args),
void *puser
)
{
        if(get_type != TYPENOTCONN && pvalue!=0 && get_count!=0){
                return ECA_ANACHRONISM;
        }
 
        return ca_search_and_connect(name_str,chixptr,conn_func,puser);
}
 


/*
 *	ca_search_and_connect()	
 */
int epicsShareAPI ca_search_and_connect
(
	const char *name_str,
 	chid *chixptr,
 	void (*conn_func) (struct connection_handler_args),
 	const void *puser
)
{
	long    status;
	ciu	chix;
	int     strcnt;

	/*
	 * make sure that chix is NULL on fail
	 */
	*chixptr = NULL;

	INITCHK;

	/* Put some reasonable limit on user supplied string size */
	strcnt = strlen(name_str) + 1;
	if (strcnt > MAX_UDP-sizeof(caHdr))
		return ECA_STRTOBIG;
	if (strcnt == 1)
		return ECA_EMPTYSTR;

	/* 
	 * only for IOCs 
	 */
#ifdef vxWorks
	{
		struct dbAddr  tmp_paddr;

		/* Find out quickly if channel is on this node  */
		status = db_name_to_addr(name_str, &tmp_paddr);
		if (status == OK) {
			int size;

			/*
			 * allocate CHIU (channel in use) block
			 *
			 * also allocate enough for the channel name & paddr
			 * block
			 */
			size = CA_MESSAGE_ALIGN(sizeof(*chix) + strcnt) +
				 sizeof(struct dbAddr);
			*chixptr = chix = (ciu) calloc(1,size);
			if (!chix){
				return ECA_ALLOCMEM;
			}
			chix->id.paddr = (struct dbAddr *) 
			(CA_MESSAGE_ALIGN(sizeof(*chix)+strcnt) + (char *)chix);
			*chix->id.paddr = tmp_paddr;
			chix->puser = puser;
			chix->pConnFunc = conn_func;
			chix->privType = chix->id.paddr->dbr_field_type;
			chix->privCount = chix->id.paddr->no_elements;
			chix->piiu = NULL; /* none */
			chix->state = cs_conn;
			chix->ar.read_access = TRUE;
			chix->ar.write_access = TRUE;
			chix->retry = 0;
			ellInit(&chix->eventq);
			strncpy((char *)(chix + 1), name_str, strcnt);

			LOCK;
			ellAdd(&local_chidlist, &chix->node);

			if (chix->pConnFunc) {
				struct connection_handler_args 	args;

				args.chid = chix;
				args.op = CA_OP_CONN_UP;
				(*chix->pConnFunc) (args);
			}
			if (chix->pAccessRightsFunc) {
				struct access_rights_handler_args 	args;

				args.chid = chix;
				args.ar = chix->ar;
				(*chix->pAccessRightsFunc) (args);
			}
			UNLOCK;
			return ECA_NORMAL;
		}
	}
#endif

	if (!ca_static->ca_piiuCast) {
		cac_create_udp_fd();
		if(!ca_static->ca_piiuCast){
			return ECA_NOCAST;
		}
	}

	/* allocate CHIU (channel in use) block */
	/* also allocate enough for the channel name */
	*chixptr = chix = (ciu) calloc(1, sizeof(*chix) + strcnt);
	if (!chix){
		return ECA_ALLOCMEM;
	}

	LOCK;
	do {
		chix->cid = CLIENT_SLOW_ID_ALLOC;
		status = bucketAddItemUnsignedId (pSlowBucket, 
					&chix->cid, chix);
	} while (status == S_bucket_idInUse);

	if (status != S_bucket_success) {
		*chixptr = (chid) NULL;
		free((char *) chix);
		UNLOCK;
		if (status == S_bucket_noMemory) {
			return ECA_ALLOCMEM;
		}
		return ECA_INTERNAL;
	}

	chix->puser = (void *) puser;
	chix->pConnFunc = conn_func;
	chix->privType = TYPENOTCONN; /* invalid initial type 	 */
	chix->privCount = 0; 	/* invalid initial count	 */
	chix->id.sid = ~0U;	/* invalid initial server id 	 */
	chix->ar.read_access = FALSE;
	chix->ar.write_access = FALSE;

	chix->state = cs_never_conn;
	ellInit(&chix->eventq);

	/* Save this channels name for retry if required */
	strncpy((char *)(chix + 1), name_str, strcnt);

	addToChanList(chix, piiuCast);

	/*
	 * reset broadcasted search counters
	 */
	ca_static->ca_conn_next_retry = ca_static->currentTime;
	cacSetRetryInterval (0u);

	/*
	 * Connection Management takes care 
	 * of sending the the search requests
	 */
	if (!chix->pConnFunc) {
		SETPENDRECV;
	}
	UNLOCK;

	return ECA_NORMAL;
}


/*
 * SEARCH_MSG()
 */
int search_msg(
ciu	chix,
int	reply_type
)
{
	int			status;
	int    			size;
	caHdr			*mptr;
	struct ioc_in_use	*piiu;

	piiu = chix->piiu;

	if (piiu!=piiuCast) {
		return ECA_INTERNAL;
	}

	size = strlen((char *)(chix+1))+1;

	LOCK;
	status = cac_alloc_msg_no_flush (piiu, size, &mptr);
	if (status != ECA_NORMAL) {
		UNLOCK;
		return status;
	}

	mptr->m_cmmd = htons (CA_PROTO_SEARCH);
	mptr->m_available = chix->cid;
	mptr->m_dataType = reply_type;
	mptr->m_count = htons (CA_MINOR_VERSION);
	mptr->m_cid = chix->cid;

	/* 
	 * channel name string - forces a NULL at the end because 
	 * strcnt is always >= strlen + 1 
	 */
	mptr++;
	strncpy((char *)mptr, (char *)(chix + 1), size);

	cac_add_msg(piiu);

	/*
	 * increment the number of times we have tried this
	 * channel
	 */
	if (chix->retry<MAXCONNTRIES) {
		chix->retry++;
	}
	if (ca_static->ca_search_tries<ULONG_MAX) {
		ca_static->ca_search_tries++;
	}

	/*
	 * move the channel to the end of the list so
	 * that all channels get a equal chance 
	 */
	ellDelete(&piiu->chidlist, &chix->node);
	ellAdd(&piiu->chidlist, &chix->node);

	UNLOCK;

	return ECA_NORMAL;
}



/*
 * CA_ARRAY_GET()
 * 
 * 
 */
int epicsShareAPI ca_array_get
(
chtype 		type,
unsigned long 	count,
chid 		chix,
void 		*pvalue
)
{
	int	status;
	miu	monix;

	CHIXCHK(chix);

	if (INVALID_DB_REQ(type))
		return ECA_BADTYPE;

	if(!chix->ar.read_access){
		return ECA_NORDACCESS;
	}

	if (count > chix->privCount) {
		return ECA_BADCOUNT;
	}

#ifdef vxWorks
	{
		int             status;

		if(!chix->piiu) {
			status = db_get_field(
					      chix->id.paddr,
					      type,
					      pvalue,
					      count,
					      NULL);
			if (status == OK)
				return ECA_NORMAL;
			else
				return ECA_GETFAIL;
		}
	}
#endif

	/*
	 * lock around io block create and list add
	 * so that we are not deleted without
	 * reclaiming the resource
	 */
	LOCK;
	monix = caIOBlockCreate();
	if (monix) {

		monix->chan = chix;
		monix->type = type;
		monix->count = count;
		monix->usr_arg = pvalue;

		ellAdd(&pend_read_list, &monix->node);
	}

	if (monix) {
		status = issue_get_callback(monix, CA_PROTO_READ);
		if (status == ECA_NORMAL) {
			SETPENDRECV;
		}
		else {
			if (ca_state(chix)==cs_conn) {
				ellDelete (&pend_read_list, &monix->node);
				caIOBlockFree (monix);
			}
		}
	} 
	else {
		status = ECA_ALLOCMEM;
	}
	UNLOCK;
	return status;
}



/*
 * CA_ARRAY_GET_CALLBACK()
 */
int epicsShareAPI ca_array_get_callback
(
chtype type,
unsigned long count,
chid chix,
void (*pfunc) (struct event_handler_args),
const void *arg
)
{
	int	status;
	miu	monix;

	CHIXCHK(chix);

	if (INVALID_DB_REQ(type))
		return ECA_BADTYPE;

	if (count > chix->privCount)
		return ECA_BADCOUNT;

	if (pfunc==NULL) {
		return ECA_BADFUNCPTR;
	}

	if(!chix->ar.read_access){
		return ECA_NORDACCESS;
	}

#ifdef vxWorks
	if (!chix->piiu) {
		struct pending_event ev;

		ev.usr_func = pfunc;
		ev.usr_arg = arg;
		ev.chan = chix;
		ev.type = type;
		ev.count = count;
		ca_event_handler(&ev, chix->id.paddr, 0, NULL);
		return ECA_NORMAL;
	}
#endif

	/*
	 * lock around io block create and list add
	 * so that we are not deleted without
	 * reclaiming the resource
	 */
	LOCK;
	monix = caIOBlockCreate();
	if (monix) {

		monix->chan = chix;
		monix->usr_func = pfunc;
		monix->usr_arg = (void *) arg;
		monix->type = type;
		monix->count = count;

		ellAdd(&pend_read_list, &monix->node);
	}

	if (monix) {
		status = issue_get_callback (monix, CA_PROTO_READ_NOTIFY);
		if (status != ECA_NORMAL) {
			if (ca_state(chix)==cs_conn) {
				ellDelete (&pend_read_list, &monix->node);
				caIOBlockFree (monix);
			}
		}
	} 
	else {
		status = ECA_ALLOCMEM;
	}
	UNLOCK;

	return status;
}


/*
 * caIOBlockCreate()
 */
LOCAL miu caIOBlockCreate(void)
{
	int	status;
	miu	pIOBlock;

	LOCK;

	pIOBlock = (miu) freeListCalloc(ca_static->ca_ioBlockFreeListPVT);
	if (pIOBlock) {
		do {
			pIOBlock->id = CLIENT_FAST_ID_ALLOC;
			status = bucketAddItemUnsignedId(
					pFastBucket, 
					&pIOBlock->id, 
					pIOBlock);
		} while (status == S_bucket_idInUse);

		if(status != S_bucket_success){
			freeListFree(ca_static->ca_ioBlockFreeListPVT, 
				pIOBlock);
			pIOBlock = NULL;
		}
	}

	UNLOCK;

	return pIOBlock;
}


/*
 * caIOBlockFree()
 */
void caIOBlockFree(miu pIOBlock)
{
	int	status;

	LOCK;
	status = bucketRemoveItemUnsignedId(
			ca_static->ca_pFastBucket, 
			&pIOBlock->id);
	assert (status == S_bucket_success);
	pIOBlock->id = ~0U; /* this id always invalid */
	freeListFree(ca_static->ca_ioBlockFreeListPVT, pIOBlock);
	UNLOCK;
}


/*
 * Free all io blocks on the list attached to the specified channel
 */
void caIOBlockListFree(
ELLLIST *pList, 
chid 	chan, 
int 	cbRequired, 
int 	status)
{
	miu				monix;
	miu				next;
	struct event_handler_args	args;

	for (monix = (miu) ellFirst (pList);
	     monix;
	     monix = next) {

		next = (miu) ellNext (&monix->node);

		if (chan == NULL || monix->chan == chan) {

			ellDelete (pList, &monix->node);

			args.usr = 	(void *) monix->usr_arg;
			args.chid = 	monix->chan;
			args.type = 	monix->type;
			args.count = 	monix->count;
			args.status = 	status;
			args.dbr = 	NULL;

			caIOBlockFree (monix);

			if (cbRequired && monix->usr_func) {
				(*monix->usr_func) (args);
			}
		}
	}
}


/*
 *
 *	ISSUE_GET_CALLBACK()
 *	(lock must be on)
 */
LOCAL int issue_get_callback(evid monix, ca_uint16_t cmmd)
{
	int			status;
	chid   			chix = monix->chan;
	ca_uint16_t        	count;
	caHdr		hdr;
	struct ioc_in_use	*piiu;

	piiu = chix->piiu;

  	/* 
	 * dont send the message if the conn is down 
	 * (it will be sent once connected)
  	 */
	if (chix->state != cs_conn) {
		return ECA_DISCONNCHID;
	}

	/*
	 * set to the native count if they specify zero
	 */
	if (monix->count == 0 || monix->count > chix->privCount){
		count = chix->privCount;
	}
	else{
		count = (ca_uint16_t) monix->count;
	}
	
	/* msg header only on db read notify req	 */
	hdr.m_cmmd = htons (cmmd);
	hdr.m_dataType = htons ((ca_uint16_t)monix->type);
	hdr.m_count = htons (count);
	hdr.m_available = monix->id;
	hdr.m_postsize = 0;
	hdr.m_cid = chix->id.sid;

	status = cac_push_msg (piiu, &hdr, 0);

	return status;
}


/*
 *	CA_ARRAY_PUT_CALLBACK()
 *
 */
int epicsShareAPI ca_array_put_callback
(
chtype				type,
unsigned long			count,
chid				chix,
const void			*pvalue,
void				(*pfunc)(struct event_handler_args),
const void			*usrarg
)
{
	IIU	*piiu;
	int	status;
	miu 	monix;

	/*
	 * valid channel id test
	 */
  	CHIXCHK(chix);

	/*
	 * compound types not allowed
	 */
  	if(dbr_value_offset[type]){
    		return ECA_BADTYPE;
	}

	if(!chix->ar.write_access){
		return ECA_NOWTACCESS;
	}

	if (pfunc==NULL) {
		return ECA_BADFUNCPTR;
	}

	/*
	 * check for valid count
	 */
  	if(count > chix->privCount || count == 0)
    		return ECA_BADCOUNT;

	piiu = chix->piiu;

	if (piiu) {
		if(!CA_V41(CA_PROTOCOL_VERSION, piiu->minor_version_number)){
			return ECA_NOSUPPORT;
		}
	}

	if (type==DBR_STRING) {
		int status;
		status = check_a_dbr_string(pvalue, count);
		if (status != ECA_NORMAL) {
			return status;
		}
	}

#ifdef vxWorks
	if (!piiu) {
		/* cast removes const */
		ciu			pChan = (ciu) chix; 
		CACLIENTPUTNOTIFY	*ppn;
		unsigned		size;

		size = dbr_size_n(type,count);
		LOCK;

		ppn = pChan->ppn;
		if(ppn){
			/*
			 * wait while it is busy
			 */
			if(ppn->busy){
				UNLOCK;
				status = semTake(
					ca_static->ca_blockSem,
					sysClkRateGet()*60);
				if(status != OK){
					return ECA_PUTCBINPROG;
				}
				LOCK;
			}

			/*
		 	 * if not busy then free the current
			 * block if it is to small
			 */
			if(ppn->valueSize<size){
				free(ppn);
				ppn = pChan->ppn = NULL;
			}
		}

		if(!ppn){
			ppn = (CACLIENTPUTNOTIFY *) 
				calloc(1, sizeof(*ppn)+size);
			if(!ppn){
				UNLOCK;
				return ECA_ALLOCMEM;
			}
			pChan->ppn = ppn;
			ppn->pcas = ca_static;
			ppn->dbPutNotify.userCallback = 
				ca_put_notify_action;
			ppn->dbPutNotify.usrPvt = pChan;
			ppn->dbPutNotify.paddr = chix->id.paddr;
			ppn->dbPutNotify.pbuffer = (ppn+1);
		}
		ppn->busy = TRUE;
        ppn->onExtraLaborQueue = FALSE;
		ppn->caUserCallback = pfunc;
		ppn->caUserArg = (void *) usrarg;
		ppn->dbPutNotify.nRequest = count;
		memcpy(ppn->dbPutNotify.pbuffer, (char *)pvalue, size);
		status = dbPutNotifyMapType(&ppn->dbPutNotify, type);
		if(status){
			UNLOCK;
			ppn->busy = FALSE;
			return ECA_PUTFAIL;
		}
		status = dbPutNotify(&ppn->dbPutNotify);
		UNLOCK;
		if(status && status != S_db_Pending){
			if(status==S_db_Blocked){
				return ECA_PUTCBINPROG;
			}
			ppn->busy = FALSE;
			return ECA_PUTFAIL;
		}
		return ECA_NORMAL;
	}
#endif /*vxWorks*/

	/*
	 * lock around io block create and list add
	 * so that we are not deleted without
	 * reclaiming the resource
	 */
	LOCK;
	monix = caIOBlockCreate();
	if (!monix) {
		UNLOCK;
		return ECA_ALLOCMEM;
	}
	ellAdd(&pend_write_list, &monix->node);
	UNLOCK;

	monix->chan = chix;
	monix->usr_func = pfunc;
	monix->usr_arg = (void *) usrarg;
	monix->type = type;
	monix->count = count;

	status = issue_ca_array_put(
			CA_PROTO_WRITE_NOTIFY, 
			monix->id,
			type, 
			count, 
			chix, 
			pvalue);
	if(status != ECA_NORMAL){
		LOCK;
		if (ca_state(chix)==cs_conn) {
			ellDelete (&pend_write_list, &monix->node);
			caIOBlockFree(monix);
		}
		UNLOCK;
		return status;
	}


  	return status;
}


/*
 * 	CA_PUT_NOTIFY_ACTION
 */
#ifdef vxWorks
LOCAL void ca_put_notify_action(PUTNOTIFY *ppn)
{
	CACLIENTPUTNOTIFY	*pcapn;
	struct ioc_in_use	*piiu;
	struct CA_STATIC	*pcas;
	chid			chix;

	/*
	 * we choose to suspend the task if there
	 * is an internal failure
	 */
	chix = (chid) ppn->usrPvt;
	if(!chix){
		taskSuspend(0);
	}
	if(!chix->ppn){
		taskSuspend(0);
	}

	piiu = chix->piiu;
	pcapn = chix->ppn;
	pcas = pcapn->pcas;

        /*
         * independent lock used here in order to
         * avoid any possibility of blocking
         * the database (or indirectly blocking
         * one client on another client).
         */
        semTake(pcas->ca_putNotifyLock, WAIT_FOREVER);
        pcapn->onExtraLaborQueue = TRUE;
        ellAdd(&pcas->ca_putNotifyQue, &pcapn->node);
        semGive(pcas->ca_putNotifyLock);

        /*
         * offload the labor for this to the
         * event task so that we never block
         * the db or another client.
         */
        db_post_extra_labor(pcas->ca_evuser);

}
#endif /*vxWorks*/



/*
 *	CA_ARRAY_PUT()
 *
 *
 */
int epicsShareAPI ca_array_put (
	chtype			type,
	unsigned long		count,
	chid			chix,
	const void		*pvalue
)
{
	/*
	 * valid channel id test
	 */
  	CHIXCHK(chix);

	/*
	 * compound types not allowed
	 */
  	if(dbr_value_offset[type]){
    		return ECA_BADTYPE;
	}

	if(!chix->ar.write_access){
		return ECA_NOWTACCESS;
	}

	/*
	 * check for valid count
	 */
  	if(count > chix->privCount || count == 0)
    		return ECA_BADCOUNT;

	if (type==DBR_STRING) {
		int status;
		status = check_a_dbr_string(pvalue, count);
		if (status != ECA_NORMAL) {
			return status;
		}
	}

#ifdef vxWorks
	/*
	 * If channel is on this client's host then
	 * call the database directly
	 */
  	if(!chix->piiu){
    		int status;

      		status = db_put_field(  chix->id.paddr,  
                               		type,   
                               		pvalue,
                               		count);            
      		if(status==OK)
			return ECA_NORMAL;
      		else
        		return ECA_PUTFAIL;
  	}
#endif /*vxWorks*/

	return issue_ca_array_put(CA_PROTO_WRITE, ~0U, type, count, chix, pvalue);
}

/*
 * check_a_dbr_string()
 */
LOCAL int check_a_dbr_string(const char *pStr, const unsigned count)
{
	unsigned i;

	for (i=0; i< count; i++) {
		unsigned int strsize;

		strsize = strlen(pStr) + 1;

		if (strsize>MAX_STRING_SIZE) {
			return ECA_STRTOBIG;
		}

		pStr += MAX_STRING_SIZE;
	}

	return ECA_NORMAL;
}



/*
 * issue_ca_array_put()
 */
LOCAL int issue_ca_array_put
(
ca_uint16_t	cmd,
unsigned	id,
chtype		type,
unsigned long	count,
chid		chix,
const void	*pvalue
)
{ 
	int status;
	struct ioc_in_use *piiu;
	caHdr hdr;
  	int postcnt;
  	unsigned size_of_one;
#	ifdef CONVERSION_REQUIRED 
  	unsigned i;
	void *pCvrtBuf;
  	void *pdest;
#	endif /*CONVERSION_REQUIRED*/

	piiu = chix->piiu;
	size_of_one = dbr_size[type];
	postcnt = dbr_size_n(type,count);

	if (type == DBR_STRING && count == 1) {
		char *pstr = (char *)pvalue;

		postcnt = strlen(pstr)+1;
	}

#	ifdef CONVERSION_REQUIRED 
		pCvrtBuf = pdest = malloc_put_convert(postcnt);
		if(!pdest){
			return ECA_ALLOCMEM;
		}

		/*
		 * No compound types here because these types are read only
		 * and therefore only appropriate for gets or monitors
		 *
		 * I changed from a for to a while loop here to avoid bounds
		 * checker pointer out of range error, and unused pointer
		 * update when it is a single element.
		 */
		i=0;
		while (TRUE) {
			switch (type) {
			case	DBR_LONG:
				*(dbr_long_t *)pdest = htonl (*(dbr_long_t *)pvalue);
				break;

			case	DBR_CHAR:
				*(dbr_char_t *)pdest = *(dbr_char_t *)pvalue;
				break;

			case	DBR_ENUM:
			case	DBR_SHORT:
			case	DBR_PUT_ACKT:
			case	DBR_PUT_ACKS:
#			if DBR_INT != DBR_SHORT
			case	DBR_INT:
#			endif /*DBR_INT != DBR_SHORT*/
				*(dbr_short_t *)pdest = htons (*(dbr_short_t *)pvalue);
				break;

			case	DBR_FLOAT:
				dbr_htonf ((dbr_float_t *)pvalue, pdest);
				break;

			case	DBR_DOUBLE: 
				dbr_htond ((dbr_double_t *)pvalue, pdest);
			break;

			case	DBR_STRING:
				/*
				 * string size checked above
				 */
				strcpy (pdest, pvalue);
				break;

			default:
				UNLOCK;
				return ECA_BADTYPE;
			}

			if (++i>=count) {
				break;
			}

			pdest = ((char *)pdest) + size_of_one;
			pvalue = ((char *)pvalue) + size_of_one;
		}

		pvalue = pCvrtBuf;

#	endif /*CONVERSION_REQUIRED*/

	hdr.m_cmmd = htons(cmd);
	hdr.m_dataType = htons((ca_uint16_t)type);
	hdr.m_count = htons((ca_uint16_t)count);
	hdr.m_cid = chix->id.sid;
	hdr.m_available = id;
	hdr.m_postsize = (ca_uint16_t) postcnt;

	status = cac_push_msg (piiu, &hdr, pvalue);

#	ifdef CONVERSION_REQUIRED
		free_put_convert (pCvrtBuf);
#	endif /*CONVERSION_REQUIRED*/

  	return status;
}


/*
 * malloc_put_convert()
 */
#ifdef CONVERSION_REQUIRED 
LOCAL void *malloc_put_convert(unsigned long size)
{
	struct putCvrtBuf	*pBuf;

	LOCK;
	pBuf = (struct putCvrtBuf *) ellFirst(&ca_static->putCvrtBuf);
	while(pBuf){
		if(pBuf->size >= size){
			break;
		}
		pBuf = (struct putCvrtBuf *) ellNext(&pBuf->node);
	}

	if(pBuf){
		ellDelete(&ca_static->putCvrtBuf, &pBuf->node);
	}
	UNLOCK;

	if(!pBuf){
		pBuf = (struct putCvrtBuf *) malloc(sizeof(*pBuf)+size);
		if(!pBuf){
			return NULL;
		}
		pBuf->size = size;
		pBuf->pBuf = (void *) (pBuf+1);
	}

	return pBuf->pBuf;
}
#endif /* CONVERSION_REQUIRED */


/*
 * free_put_convert()
 */
#ifdef CONVERSION_REQUIRED 
LOCAL void free_put_convert(void *pBuf)
{
	struct putCvrtBuf	*pBufHdr;

	pBufHdr = (struct putCvrtBuf *)pBuf;
	pBufHdr -= 1;
	assert(pBufHdr->pBuf == (void *)(pBufHdr+1));

	LOCK;
	ellAdd(&ca_static->putCvrtBuf, &pBufHdr->node);
	UNLOCK;

	return;
}
#endif /* CONVERSION_REQUIRED */


/*
 *	Specify an event subroutine to be run for connection events
 *
 */
int epicsShareAPI ca_change_connection_event
(
chid		chix,
void 		(*pfunc)(struct connection_handler_args)
)
{
	ciu	pChan = (ciu) chix; /* remove const */

  	INITCHK;
  	LOOSECHIXCHK(pChan);

	if(pChan->pConnFunc == pfunc)
  		return ECA_NORMAL;

  	LOCK;
	if (pChan->state==cs_never_conn) {
		if(!pChan->pConnFunc){
    			CLRPENDRECV;
		}
		if(!pfunc){
    			SETPENDRECV;
		}
	}
  	pChan->pConnFunc = pfunc;
  	UNLOCK;

  	return ECA_NORMAL;
}


/*
 * ca_replace_access_rights_event
 */
int epicsShareAPI ca_replace_access_rights_event(
chid 	chan, 
void    (*pfunc)(struct access_rights_handler_args))
{
	ciu pChan = (ciu) chan; /* remove const */
	struct access_rights_handler_args 	args;

  	INITCHK;
  	LOOSECHIXCHK(pChan);

    	pChan->pAccessRightsFunc = pfunc;

	/*
	 * make certain that it runs at least once
	 */
	if(pChan->state == cs_conn && pChan->pAccessRightsFunc){
		args.chid = chan;
		args.ar = chan->ar;
		(*pChan->pAccessRightsFunc)(args);
	}

  	return ECA_NORMAL;
}


/*
 *	Specify an event subroutine to be run for asynch exceptions
 *
 */
int epicsShareAPI ca_add_exception_event
(
	void 		(*pfunc)(struct exception_handler_args),
	const void 	*arg
)
{

  	INITCHK;
  	LOCK;
  	if (pfunc) {
    		ca_static->ca_exception_func = pfunc;
    		ca_static->ca_exception_arg = arg;
  	}
  	else {
    		ca_static->ca_exception_func = ca_default_exception_handler;
    		ca_static->ca_exception_arg = NULL;
  	}
  	UNLOCK;

  	return ECA_NORMAL;
}


/*
 *	CA_ADD_MASKED_ARRAY_EVENT
 *
 *
 */
int epicsShareAPI ca_add_masked_array_event
(
chtype 		type,
unsigned long	count,
chid 		chix,
void 		(*ast)(struct event_handler_args),
const void 	*astarg,
ca_real		p_delta,
ca_real		n_delta,
ca_real		timeout,
evid 		*monixptr,
long		mask
)
{
	ciu 	pChan = (ciu) chix; /* remove const */
  	miu	monix;
  	int	status;

  	INITCHK;
  	LOOSECHIXCHK(pChan);

  	if(INVALID_DB_REQ(type))
    		return ECA_BADTYPE;

	/*
	 * Check for huge waveform
	 *
	 * (the count is not checked here against the native count
	 * when connected because this introduces a race condition 
	 * for the client tool - the requested count is clipped to 
	 * the actual count when the monitor request is sent so
	 * verifying that the requested count is valid here isnt
	 * required)
	 */
	if(dbr_size_n(type,count)>MAX_MSG_SIZE-sizeof(caHdr)){
		return ECA_TOLARGE;
	}

	if (ast==NULL) {
		return ECA_BADFUNCPTR;
	}

  	if(!mask)
    		return ECA_BADMASK;

	/*
	 * lock around io block create and list add
	 * so that we are not deleted without
	 * reclaiming the resource
	 */
  	LOCK;

  	if (!pChan->piiu) {
# 		ifdef vxWorks
			monix = freeListMalloc (ca_static->ca_dbMonixFreeList);
# 		else
		return ECA_INTERNAL;
# 			endif
  	}
	else {
		monix = caIOBlockCreate();
	}

  	if(!monix){
    		UNLOCK;
    		return ECA_ALLOCMEM;
  	}

  	/* they dont have to supply one if they dont want to */
  	if(monixptr){
    		*monixptr = monix;
	}

  	monix->chan	= chix;
  	monix->usr_func	= ast;
  	monix->usr_arg	= (void *) astarg;
  	monix->type	= type;
  	monix->count	= count;
  	monix->p_delta	= p_delta;
  	monix->n_delta	= n_delta;
  	monix->timeout	= timeout;
  	monix->mask	= (unsigned short) mask;

# 	ifdef vxWorks
	if(!pChan->piiu){
		status = db_add_event(	
				evuser,
				pChan->id.paddr,
				ca_event_handler,
				monix,
				mask,
				(struct event_block *)(monix+1));
		if(status == ERROR){
			UNLOCK;
			freeListFree (ca_static->ca_dbMonixFreeList, monix);
			return ECA_DBLCLFAIL; 
		}

		/* 
		 * Place in the channel list 
		 * - do it after db_add_event so there
		 * is no chance that it will be deleted 
		 * at exit before it is completely created
		 */
		ellAdd(&pChan->eventq, &monix->node);

		/* 
		 * force event to be called at least once
		 * return warning msg if they have made the queue to full 
		 * to force the first (untriggered) event.
		 */
		if(db_post_single_event((struct event_block *)(monix+1))==ERROR){
			UNLOCK;
			return ECA_OVEVFAIL;
		}

		UNLOCK;
		return ECA_NORMAL;
	} 
# 	endif

  	/* It can be added to the list any place if it is remote */
  	/* Place in the channel list */
  	ellAdd(&pChan->eventq, &monix->node);

  	UNLOCK;

	if(pChan->state == cs_conn){
  		status = ca_request_event(monix);
		if (status != ECA_NORMAL) {
			LOCK;
			if (ca_state(pChan)==cs_conn) {
				ellDelete (&pChan->eventq, &monix->node);
				caIOBlockFree(monix);
			}
			UNLOCK
		}
		return status;
	}
	else{
		return ECA_NORMAL;
	}
}


/*
 *	CA_REQUEST_EVENT()
 */
int ca_request_event(evid monix)
{
	int			status;
	chid   			chix = monix->chan;
	ca_uint16_t        	count;
	struct monops		msg;
	struct ioc_in_use	*piiu;
	ca_float32_t		p_delta;
	ca_float32_t		n_delta;
	ca_float32_t		tmo;
	
	piiu = chix->piiu;

  	/* 
	 * dont send the message if the conn is down 
	 * (it will be sent once connected)
  	 */
	if(chix->state != cs_conn){
		return ECA_DISCONNCHID;
	}

	/*
	 * clip to the native count and set to the native count if they
	 * specify zero
	 */
	if (monix->count > chix->privCount || monix->count == 0){
		count = chix->privCount;
	}
	else{
		count = (ca_uint16_t) monix->count;
	}

	/* msg header	 */
	msg.m_header.m_cmmd = htons(CA_PROTO_EVENT_ADD);
	msg.m_header.m_available = monix->id;
	msg.m_header.m_dataType = htons((ca_uint16_t)monix->type);
	msg.m_header.m_count = htons(count);
	msg.m_header.m_cid = chix->id.sid;
	msg.m_header.m_postsize = sizeof(msg.m_info);

	/* msg body	 */
	p_delta = (ca_float32_t) monix->p_delta;
	n_delta = (ca_float32_t) monix->n_delta;
	tmo = (ca_float32_t) monix->timeout;
	dbr_htonf(&p_delta, &msg.m_info.m_hval);
	dbr_htonf(&n_delta, &msg.m_info.m_lval);
	dbr_htonf(&tmo, &msg.m_info.m_toval);
	msg.m_info.m_mask = htons(monix->mask);
	msg.m_info.m_pad = 0; /* allow future use */	

	status = cac_push_msg(piiu, &msg.m_header, &msg.m_info);

	return status;
}


/*
 *
 *	CA_EVENT_HANDLER()
 *	(only for local (for now vxWorks) clients)
 *
 */
#ifdef vxWorks
LOCAL void ca_event_handler(
void		*usrArg,
struct dbAddr	*paddr,
int		hold,
db_field_log	*pfl
)
{
	miu monix = (miu) usrArg;
  	int status;
  	int count;
  	union db_access_val valbuf;
  	void *pval;
  	unsigned size;
	struct tmp_buff{
		ELLNODE node;
		unsigned size;
	};
	struct tmp_buff *pbuf = NULL;

  	/*
  	 * clip to the native count
  	 * and set to the native count if they specify zero
  	 */
  	if(monix->count > monix->chan->privCount || monix->count == 0){
		count = monix->chan->privCount;
	}
	else{
		count = monix->count;
	}

	size = dbr_size_n(monix->type,count);

	if( size <= sizeof(valbuf) ){
		pval = (void *) &valbuf;
	}
	else{

		/*
		 * find a preallocated block which fits
		 * (stored with largest block first)
		 */
		LOCK;
		pbuf = (struct tmp_buff *)
				lcl_buff_list.node.next;
		if(pbuf && pbuf->size >= size){
			ellDelete(
				&lcl_buff_list,
				&pbuf->node);
		}else
			pbuf = NULL;
		UNLOCK;

		/* 
		 * test again so malloc is not inside LOCKED
		 * section
		 */
		if(!pbuf){
			pbuf = (struct tmp_buff *)
				malloc(sizeof(*pbuf)+size);
			if(!pbuf){
				ca_printf("%s: No Mem, Event Discarded\n",
					__FILE__);
				return;
			}
			pbuf->size = size;
		}
		pval = (void *) (pbuf+1);
	}
	
	status = db_get_field(	paddr,
				monix->type,
				pval,
				count,
				pfl);

  	/*
	 * Call user's callback
   	 */
	LOCK;
	if (monix->usr_func) {
		struct event_handler_args args;
		
		args.usr = (void *) monix->usr_arg;
		args.chid = monix->chan;
		args.type = monix->type;
		args.count = count;
		args.dbr = pval;

  		if(status == OK){
			args.status = ECA_NORMAL;
		}
		else{
			args.status = ECA_GETFAIL;
		}

   	 	(*monix->usr_func)(args);
	}

	/*
	 *
	 * insert the buffer back into the que in size order if
	 * one was used.
	 */
	if(pbuf){
		struct tmp_buff		*ptbuf;

		for(	ptbuf = (struct tmp_buff *) lcl_buff_list.node.next;
			ptbuf;
			ptbuf = (struct tmp_buff *) pbuf->node.next){

			if(ptbuf->size <= pbuf->size){
				break;
			}
		}
		if(ptbuf)
			ptbuf = (struct tmp_buff *) ptbuf->node.previous;

		ellInsert(	
			&lcl_buff_list,
			&ptbuf->node,
			&pbuf->node);
	}
	UNLOCK;

  	return;
}
#endif



/*
 *
 *	CA_CLEAR_EVENT(MONIX)
 *
 *	Cancel an outstanding event for a channel.
 *
 *	NOTE: returns before operation completes in the server 
 *	(and the client). 
 *	This is a good reason not to allow them to make the monix 
 *	block as part of a larger structure.
 *	Nevertheless the caller is gauranteed that his specified
 *	event is disabled and therefore will not run (from this source)
 *	after leaving this routine.
 *
 */
int epicsShareAPI ca_clear_event (evid monix)
{
	ciu   	chix = (ciu) monix->chan; /* cast removes const */
	miu	pMon = (miu) monix; /* cast removes const */
	int	status;
	caHdr 	hdr;
	evid	lkup;

	/*
	 * is it a valid channel ?
	 */
	LOOSECHIXCHK(chix);

	/*
	 * is it a valid monitor id
	 */
	if (chix->piiu) {
		LOCK;
		lkup = (evid) bucketLookupItemUnsignedId(
					pFastBucket,
					&pMon->id);
		UNLOCK;
		if (lkup != pMon) {
			return ECA_BADMONID;
		}
	}

	/* disable any further events from this event block */
	pMon->usr_func = NULL;

#ifdef vxWorks
	if (!chix->piiu) {
		register int    status;

		/*
		 * dont allow two threads to delete the same moniitor at once
		 */
		LOCK;
		ellDelete(&chix->eventq, &pMon->node);
		status = db_cancel_event((struct event_block *)(pMon + 1));
		UNLOCK;
		freeListFree (ca_static->ca_dbMonixFreeList, pMon);

		return ECA_NORMAL;
	}
#endif /*vxWorks*/

	/*
	 * dont send the message if the conn is down (just delete from the
	 * queue and return)
	 * 
	 * check for conn down while locked to avoid a race
	 */
	if(chix->state == cs_conn){
		struct ioc_in_use 	*piiu;
		
		piiu = chix->piiu;

		/* msg header	 */
		hdr.m_cmmd = htons(CA_PROTO_EVENT_CANCEL);
		hdr.m_available = pMon->id;
		hdr.m_dataType = htons(chix->privType);
		hdr.m_count = htons(chix->privCount);
		hdr.m_cid = chix->id.sid;
		hdr.m_postsize = 0;
	
		status = cac_push_msg(piiu, &hdr, NULL);

		/* 
		 *	NOTE: I free the monitor block only
		 *	after confirmation from IOC 
		 */
	}
	else{
		LOCK;
		ellDelete(&chix->eventq, &pMon->node);
		caIOBlockFree(pMon);
		UNLOCK;
		status = ECA_NORMAL;
	}

	return status;
}


/*
 *
 *	CA_CLEAR_CHANNEL(CHID)
 *
 *	clear the resources allocated for a channel by search
 *
 *	NOTE: returns before operation completes in the server 
 *	(and the client). 
 *	This is a good reason not to allow them to make the monix 
 *	block part of a larger structure.
 *	Nevertheless the caller is gauranteed that his specified
 *	event is disabled and therefore will not run 
 *	(from this source) after leaving this routine.
 *
 */
int epicsShareAPI ca_clear_channel (chid pChan)
{
	miu			monix;
	int			status;
	struct ioc_in_use 	*piiu = pChan->piiu;
	caHdr 			hdr;
	caCh			*pold_ch;
	enum channel_state	old_chan_state;

	if (pChan->piiu) {
		pChan = bucketLookupItemUnsignedId
					(ca_static->ca_pSlowBucket, &pChan->cid);
		if (pChan == NULL) {
			return ECA_BADCHID;
		}
	}
	else {
		LOOSECHIXCHK(pChan);
	}

	/* disable their further use of deallocated channel */
	pChan->privType = TYPENOTINUSE;
	old_chan_state = pChan->state;
	pChan->state = cs_closed;
	pChan->pAccessRightsFunc = NULL;
	pold_ch = pChan->pConnFunc;
	pChan->pConnFunc = NULL;

	/* the while is only so I can break to the lock exit */
	LOCK;
	/* disable any further events from this channel */
	for (monix = (miu) pChan->eventq.node.next;
	     monix;
	     monix = (miu) monix->node.next)
		monix->usr_func = NULL;
	/* disable any further get callbacks from this channel */
	for (monix = (miu) pend_read_list.node.next;
	     monix;
	     monix = (miu) monix->node.next)
		if (monix->chan == pChan)
			monix->usr_func = NULL;
	/* disable any further put callbacks from this channel */
	for (monix = (miu) pend_write_list.node.next;
	     monix;
	     monix = (miu) monix->node.next)
		if (monix->chan == pChan)
			monix->usr_func = NULL;

#ifdef vxWorks
	if (!pChan->piiu) {
		CACLIENTPUTNOTIFY	*ppn;
		int             	status;

		/*
		 * clear out the events for this channel
		 */
		while ( (monix = (miu) ellGet(&pChan->eventq)) ) {
			status = db_cancel_event((struct event_block *)(monix + 1));
			assert (status == OK);
			freeListFree (ca_static->ca_dbMonixFreeList, monix);
		}

		/*
		 * cancel any outstanding put notifies
		 */
		if(pChan->ppn){
			ppn = pChan->ppn;
			if(ppn->busy){
				dbNotifyCancel(&ppn->dbPutNotify);
			}

            semTake (ca_static->ca_putNotifyLock, WAIT_FOREVER);
            if ( ppn->onExtraLaborQueue ) {
                ellDelete( &ca_static->ca_putNotifyQue, &ppn->node );
            }
		    semGive (ca_static->ca_putNotifyLock);

            status = db_flush_extra_labor_event (ca_static->ca_evuser );
            assert ( status == OK );

			free(ppn);
		}

		/*
		 * clear out this channel
		 */
		ellDelete(&local_chidlist, &pChan->node);
		free((char *) pChan);

		UNLOCK;
		return ECA_NORMAL;
	}
#endif

	/*
	 * if this channel does not have a connection handler
	 * and it has not connected for the first time then clear the
	 * outstanding IO count
	 */
	if (old_chan_state == cs_never_conn && !pold_ch) {
		CLRPENDRECV;
	}

	/*
	 * dont send the message if not conn 
	 * (just delete from the queue and return)
	 * 
	 * check for conn state while locked to avoid a race
	 */
	if (old_chan_state != cs_conn) {
		UNLOCK;
		clearChannelResources (pChan->cid);
		return ECA_NORMAL;
	}

	/*
	 * clear events and all other resources for this chid on the
	 * IOC
	 */

	/* msg header	 */
	hdr.m_cmmd = htons(CA_PROTO_CLEAR_CHANNEL);
	hdr.m_available = pChan->cid;
	hdr.m_cid = pChan->id.sid;
	hdr.m_dataType = htons(0);
	hdr.m_count = htons(0);
	hdr.m_postsize = 0;

	status = cac_push_msg(piiu, &hdr, NULL);

	/*
	 * NOTE: I free the chid and monitor blocks only after
	 * confirmation from IOC
	 */

	UNLOCK;

	return status;
}


/*
 * ca_cidToChid()
 */
chid epicsShareAPI ca_cidToChid(unsigned id)
{
	return (chid) bucketLookupItemUnsignedId(ca_static->ca_pSlowBucket, &id);
}


/*
 * clearChannelResources()
 */
void clearChannelResources(unsigned id)
{
	struct ioc_in_use       *piiu;
	ciu			chix;
	int			status;

	LOCK;

	chix = bucketLookupItemUnsignedId (ca_static->ca_pSlowBucket, &id);
	if (chix==NULL) {
		UNLOCK;
		genLocalExcep (ECA_BADCHID,"clearChannelResources()");
		return;
	}

	piiu = chix->piiu;

	/*
	 * remove any orphaned get callbacks for this channel
	 */
	caIOBlockListFree (&ca_static->ca_pend_read_list, chix, FALSE, ECA_INTERNAL);

	/*
	 * remove any orphaned put callbacks for this channel
	 */
	caIOBlockListFree (&ca_static->ca_pend_write_list, chix, FALSE, ECA_INTERNAL);

	/*
	 * remove any monitors still attached to this channel
	 */
	caIOBlockListFree (&chix->eventq, NULL, FALSE, ECA_INTERNAL);

	status = bucketRemoveItemUnsignedId (
			ca_static->ca_pSlowBucket, &chix->cid);
	assert (status == S_bucket_success);
	removeFromChanList (chix);
	free (chix);
	
	UNLOCK;
}



/************************************************************************/
/*	This routine pends waiting for channel events and calls the 	*/
/*	timeout is specified as 0 infinite timeout is assumed.		*/
/*	if the argument early is specified TRUE then CA_NORMAL is 	*/
/*	returned early (prior to timeout experation) when outstanding 	*/
/*	IO completes.							*/
/*	Output buffers are flushed by this routine			*/
/************************************************************************/
int epicsShareAPI ca_pend (ca_real timeout, int early)
{
	struct timeval	beg_time;
	ca_real		delay;
	struct timeval	tmo;

  	INITCHK;

	if (EVENTLOCKTEST) {
		return ECA_EVDISALLOW;
	}

	cac_gettimeval (&ca_static->currentTime);

  	/*	
	 * Flush the send buffers
	 * (guarantees that we wait for all send buffer to be 
	 * flushed even if this requires blocking)
	 */
	ca_static->ca_flush_pending = TRUE;

    if(pndrecvcnt==0u && early){
		/*
		 * force the flush
		 */
		CLR_CA_TIME (&tmo);
		cac_mux_io(&tmo, TRUE);
        	return ECA_NORMAL;
	}

	if(timeout<0.0){
		/*
		 * force the flush
		 */
		CLR_CA_TIME (&tmo);
		cac_mux_io(&tmo, TRUE);
		return ECA_TIMEOUT;
	}

	beg_time = ca_static->currentTime;
	delay = 0.0;
  	while(TRUE){
		ca_real 	remaining;

    	if (pndrecvcnt==0 && early) {
			/*
			 * force the flush
			 */
			CLR_CA_TIME (&tmo);
			cac_mux_io(&tmo, TRUE);
        	return ECA_NORMAL;
		}
    	if(timeout == 0.0){
			remaining = cac_fetch_poll_period();
		}
		else{
			remaining = timeout-delay;
			/*
			 * Allow for CA background labor
			 */
			remaining = min(cac_fetch_poll_period(), remaining);
  		}    


		/*
		 * If we are not waiting for any significant delay
		 * then force the delay to zero so that we avoid
		 * scheduling delays (which can be substantial
		 * on some os)
		 */
#		ifdef CLOCKS_PER_SEC
#			define CAC_SIGNIF_INTERVAL (1.0/CLOCKS_PER_SEC)
#		else
			/*
			 * we guess (because gcc does not provide
			 * CLOCKS_PER_SEC under sunos4)
			 */
#			define CAC_SIGNIF_INTERVAL (1.0/1000000)
#		endif
		if (remaining <= CAC_SIGNIF_INTERVAL) {
			if(early){
				ca_pend_io_cleanup();
				ca_static->ca_flush_pending = TRUE;
			}
			/* 
			 * be certain that we processed
			 * recv backlog at least once
			 */
			/*
			 * force the flush
			 */
			CLR_CA_TIME (&tmo);
			/*
			 * unfortunately this causes additional messages
			 * to be read and so it is possible in rare circumstances
			 * for ECA_TIMEOUT to be returned when the IO completed
			 * during the pend io timeout clean up phase.
			 */
			cac_block_for_io_completion (&tmo);
			return ECA_TIMEOUT;
		}
		tmo.tv_sec = (long) remaining;
		tmo.tv_usec = (long) ((remaining-tmo.tv_sec)*USEC_PER_SEC);
		cac_block_for_io_completion (&tmo);

		if (timeout != 0.0) {
			delay = cac_time_diff (&ca_static->currentTime, 
							&beg_time);
		}
	}
}

/*
 * cac_fetch_poll_period()
 */
double cac_fetch_poll_period(void)
{
	if (!piiuCast) {
		return SELECT_POLL_NO_SEARCH;
	}
	else if (ellCount(&piiuCast->chidlist)==0) {
		return SELECT_POLL_NO_SEARCH;
	}
	else {
		return SELECT_POLL_SEARCH;
	}
}


/*
 * cac_time_diff()
 */
ca_real cac_time_diff (ca_time *pTVA, ca_time *pTVB)
{
        ca_real         delay;
        ca_real         udelay;

        /*
         * works with unsigned tv_sec in struct timeval
         * under HPUX
         */
        if (pTVA->tv_sec>pTVB->tv_sec) {
                delay = pTVA->tv_sec - pTVB->tv_sec;
        }
        else {
                delay = pTVB->tv_sec - pTVA->tv_sec;
                delay = -delay;
        }

        if(pTVA->tv_usec>pTVB->tv_usec){
                udelay = pTVA->tv_usec - pTVB->tv_usec;
        }
        else{
                delay -= 1.0;
                udelay = (USEC_PER_SEC - pTVB->tv_usec) + pTVA->tv_usec;
        }
        delay += udelay / USEC_PER_SEC;

        return delay;
}


/*
 * cac_time_sum()
 */
ca_time cac_time_sum (ca_time *pTVA, ca_time *pTVB)
{
	long	usum;
	ca_time	sum;

	sum.tv_sec = pTVA->tv_sec + pTVB->tv_sec;
	usum = pTVA->tv_usec +  pTVB->tv_usec;	
	if(usum>=USEC_PER_SEC){
		sum.tv_sec++;
		sum.tv_usec = usum-USEC_PER_SEC;
	}
	else{
		sum.tv_usec = usum;
	}

	return sum;
}

/*
 * noopConnHandler()
 * This is installed into channels which dont have
 * a connection handler when ca_pend_io() times
 * out so that we will not decrement the pending
 * recv count in the future.
 */
LOCAL void noopConnHandler(struct  connection_handler_args args)
{
}

/*
 *
 * set pending IO count back to zero and
 * send a sync to each IOC and back. dont
 * count reads until we recv the sync
 *
 */
LOCAL void ca_pend_io_cleanup()
{
	struct ioc_in_use *piiu;
	ciu pchan;

	LOCK;
	pchan = (ciu) ellFirst (&piiuCast->chidlist);
	while (pchan) {
		if (!pchan->pConnFunc) {
			pchan->pConnFunc = noopConnHandler;
		}
		pchan = (ciu) ellNext (&pchan->node);
	}
	for(	piiu = (IIU *) iiuList.node.next;
		piiu;
		piiu = (IIU *) piiu->node.next){

		caHdr hdr;

		if (piiu->state!=iiu_connected || piiu==piiuCast) {
			continue;
		}

		piiu->cur_read_seq++;

		hdr = nullmsg;
		hdr.m_cmmd = htons(CA_PROTO_READ_SYNC);
		cac_push_msg(piiu, &hdr, NULL);
	}
	UNLOCK;
	pndrecvcnt = 0u;
}



/*
 *	CA_FLUSH_IO()
 * 	reprocess connection state and
 * 	flush the send buffer 
 */
int epicsShareAPI ca_flush_io()
{
	struct timeval  	timeout;

  	INITCHK;

	/*
	 * Wait for all send buffers to be flushed
	 * while performing socket io and processing recv backlog
	 */
	ca_static->ca_flush_pending = TRUE;
	CLR_CA_TIME (&timeout);
	cac_mux_io (&timeout, TRUE);

  	return ECA_NORMAL;
}


/*
 *	CA_TEST_IO ()
 *
 */
int epicsShareAPI ca_test_io()
{
    	if(pndrecvcnt==0u){
        	return ECA_IODONE;
	}
	else{
		return ECA_IOINPROGRESS;
	}
}


/*
 * genLocalExcepWFL ()
 * (generate local exception with file and line number)
 */
void genLocalExcepWFL (long stat, char *ctx, char *pFile, unsigned lineNo)
{
	struct exception_handler_args args;

	/*
	* NOOP if they disable exceptions
	*/
	if (!ca_static->ca_exception_func) {
	    return;
	}
 
	args.usr = (void *) ca_static->ca_exception_arg;
	args.chid = NULL;
	args.type = -1;
	args.count = 0u;
	args.addr = NULL;
	args.stat = stat;
	args.op = CA_OP_OTHER;
	args.ctx = ctx;
	args.pFile = pFile;
	args.lineNo = lineNo;
 
	LOCK;
	(*ca_static->ca_exception_func) (args);
	UNLOCK;
}


/*
 *	CA_SIGNAL()
 */
void epicsShareAPI ca_signal(long ca_status, const char *message)
{
	ca_signal_with_file_and_lineno(ca_status, message, NULL, 0);
}


/*
 * ca_message (long ca_status)
 *
 * - if it is an unknown error code then it possible
 * that the error string generated below 
 * will be overwritten before (or while) the caller
 * of this routine is calling this routine
 * (if they call this routine again).
 */
READONLY char * epicsShareAPI ca_message (long ca_status)
{
	unsigned msgNo = CA_EXTRACT_MSG_NO(ca_status);

  	if( msgNo < NELEMENTS(ca_message_text) ){
		return ca_message_text[msgNo];
	}
	else {
		sprintf(ca_static->ca_new_err_code_msg_buf, 
	"new CA message number %u known only by server - see caerr.h", 
			msgNo);
		return ca_static->ca_new_err_code_msg_buf; 
	}
}


/*
 * ca_signal_with_file_and_lineno()
 */
void epicsShareAPI ca_signal_with_file_and_lineno(
long		ca_status, 
const char	*message, 
const char	*pfilenm, 
int		lineno)
{
  static const char  *severity[] = 
		{
		"Warning",
		"Success",
		"Error",
		"Info",
		"Fatal",
		"Fatal",
		"Fatal",
		"Fatal"
		};

  ca_printf(
"CA.Client.Diagnostic..............................................\n");

  ca_printf(
"    Message: \"%s\"\n", ca_message(ca_status));

  if(message)
    ca_printf(
"    Severity: \"%s\" Context: \"%s\"\n", 
	severity[CA_EXTRACT_SEVERITY(ca_status)],
	message);
  else
    ca_printf(
"    Severity: %s\n", severity[CA_EXTRACT_SEVERITY(ca_status)]);

  if(pfilenm){
	ca_printf(
"    Source File: %s Line Number: %d\n",
		pfilenm,
		lineno);	
  }

  /*
   *
   *
   *	Terminate execution if unsuccessful
   *
   *
   */
  if( !(ca_status & CA_M_SUCCESS) && 
		CA_EXTRACT_SEVERITY(ca_status) != CA_K_WARNING ){
      abort();
  }

  ca_printf(
"..................................................................\n");


}





/*
 *	CA_BUSY_MSG()
 *
 */
int ca_busy_message(struct ioc_in_use *piiu)
{
	caHdr hdr;
	int status;

	if(!piiu){
		return ECA_INTERNAL;
	}

  	/* 
	 * dont broadcast
	 */
    	if(piiu == piiuCast){
		return ECA_INTERNAL;
	}

	hdr = nullmsg;
	hdr.m_cmmd = htons(CA_PROTO_EVENTS_OFF);
	
	status = cac_push_msg_no_block(piiu, &hdr, NULL);
	if (status == ECA_NORMAL) {
		piiu->pushPending = TRUE;
	}
	return status;
}


/*
 * CA_READY_MSG()
 * 
 */
int ca_ready_message(struct ioc_in_use *piiu)
{
	caHdr hdr;
	int status;

	if(!piiu){
		return ECA_INTERNAL;
	}

  	/* 
	 * dont broadcast
	 */
    	if(piiu == piiuCast){
		return ECA_INTERNAL;
	}

	hdr = nullmsg;
	hdr.m_cmmd = htons(CA_PROTO_EVENTS_ON);
	
	status = cac_push_msg_no_block(piiu, &hdr, NULL);
	if (status == ECA_NORMAL) {
		piiu->pushPending = TRUE;
	}
	return status;
}


/*
 *
 * echo_request (lock must be on)
 * 
 */
int echo_request(struct ioc_in_use *piiu, ca_time *pCurrentTime)
{
	caHdr  		hdr;
	int 		status;

	hdr.m_cmmd = htons(CA_PROTO_ECHO);
	hdr.m_dataType = htons(0);
	hdr.m_count = htons(0);
	hdr.m_cid = htons(0);
	hdr.m_available = htons(0);
	hdr.m_postsize = 0u;

	/*
	 * If we are out of buffer space then postpone this
	 * operation until later. This avoids any possibility
	 * of a push pull deadlock (since this can be sent when 
	 * parsing the UDP input buffer).
	 */
	status = cac_push_msg_no_block(piiu, &hdr, NULL);
	if (status == ECA_NORMAL) {
		piiu->echoPending = TRUE;
		piiu->pushPending = TRUE;
		piiu->timeAtEchoRequest = *pCurrentTime;
	}

	return status;
}


/*
 *
 * NOOP_MSG (lock must be on)
 * 
 */
void noop_msg(struct ioc_in_use *piiu)
{
	caHdr  	hdr;
	int 	status;

	hdr.m_cmmd = htons(CA_PROTO_NOOP);
	hdr.m_dataType = htons(0);
	hdr.m_count = htons(0);
	hdr.m_cid = htons(0);
	hdr.m_available = htons(0);
	hdr.m_postsize = 0;
	
	status = cac_push_msg_no_block(piiu, &hdr, NULL);
	if (status == ECA_NORMAL) {
		piiu->pushPending = TRUE;
	}
}


/*
 * ISSUE_IOC_HOST_NAME
 * (lock must be on)
 * 
 */
void issue_client_host_name(struct ioc_in_use *piiu)
{
	unsigned	size;
	caHdr  	hdr;
	char		*pName;

	if(!piiu){
		return;
	}

  	/* 
	 * dont broadcast client identification protocol
	 */
    	if(piiu == piiuCast){
		return;
	}

	/*
	 * prior to version 4.1 client identification
	 * protocol causes a disconnect - so
	 * dont send it in this case
	 */
	if(!CA_V41(CA_PROTOCOL_VERSION, piiu->minor_version_number)){
		return;
	}

	/*
	 * allocate space in the outgoing buffer
	 */
	pName =	ca_static->ca_pHostName, 
	size = strlen(pName)+1;
	hdr = nullmsg;
	hdr.m_cmmd = htons(CA_PROTO_HOST_NAME);
	hdr.m_postsize = size;
	
	cac_push_msg(piiu, &hdr, pName);

	return;
}


/*
 * ISSUE_IDENTIFY_CLIENT (lock must be on)
 * 
 */
void issue_identify_client(struct ioc_in_use *piiu)
{
	unsigned	size;
	caHdr  	hdr;
	char		*pName;

	if(!piiu){
		return;
	}

  	/* 
	 * dont broadcast client identification protocol
	 */
    	if(piiu == piiuCast){
		return;
	}

	/*
	 * prior to version 4.1 client identification
	 * protocol causes a disconnect - so
	 * dont send it in this case
	 */
	if(!CA_V41(CA_PROTOCOL_VERSION, piiu->minor_version_number)){
		return;
	}

	/*
	 * allocate space in the outgoing buffer
	 */
	pName =	ca_static->ca_pUserName, 
	size = strlen(pName)+1;
	hdr = nullmsg;
	hdr.m_cmmd = htons(CA_PROTO_CLIENT_NAME);
	hdr.m_postsize = size;
	
	cac_push_msg(piiu, &hdr, pName);

	return;
}


/*
 * ISSUE_CLAIM_CHANNEL 
 */
int issue_claim_channel (chid pchan)
{
	IIU		*piiu = (IIU *) pchan->piiu;
	caHdr  		hdr;
	unsigned	size;
	const char 	*pStr;
	int		status;

	LOCK;

	if (!piiu) {
		ca_printf("CAC: nill piiu claim attempted?\n");
		UNLOCK;
		return ECA_INTERNAL;
	}

  	/* 
	 * dont broadcast
	 */
    if (piiu == piiuCast) {
		ca_printf("CAC: UDP claim attempted?\n");
		UNLOCK;
		return ECA_INTERNAL;
	}

	if (!pchan->claimPending) {
		ca_printf("CAC: duplicate claim attempted (while disconnected)?\n");
		UNLOCK;
		return ECA_INTERNAL;
	}

	if (pchan->state==cs_conn) {
		ca_printf("CAC: duplicate claim attempted (while connected)?\n");
		UNLOCK;
		return ECA_INTERNAL;
	}

	hdr = nullmsg;
	hdr.m_cmmd = htons(CA_PROTO_CLAIM_CIU);

	if (CA_V44(CA_PROTOCOL_VERSION, piiu->minor_version_number)) {
		hdr.m_cid = pchan->cid;
		pStr = ca_name(pchan);
		size = strlen(ca_name(pchan))+1u;
	}
	else {
		hdr.m_cid = pchan->id.sid;
		pStr = NULL;
		size = 0u;
	}

	hdr.m_postsize = size;

	/*
	 * The available field is used (abused)
	 * here to communicate the minor version number
	 * starting with CA 4.1.
	 */
	hdr.m_available = htonl(CA_MINOR_VERSION);

	/*
	 * If we are out of buffer space then postpone this
	 * operation until later. This avoids any possibility
	 * of a push pull deadlock (since this is sent when 
	 * parsing the UDP input buffer).
	 */
	status = cac_push_msg_no_block(piiu, &hdr, pStr); 
	
	if (status == ECA_NORMAL) {

		/*
		 * move to the end of the list once the claim has been sent
		 */
		pchan->claimPending = FALSE;
		ellDelete (&piiu->chidlist, &pchan->node);
		ellAdd (&piiu->chidlist, &pchan->node);

		if (!CA_V42(CA_PROTOCOL_VERSION, piiu->minor_version_number)) {
			cac_reconnect_channel(pchan->cid, TYPENOTCONN, 0);
		}
	}
	else {
		piiu->claimsPending = TRUE;
	}
	UNLOCK;

	return status;
}


/*
 *
 *	Default Exception Handler
 *
 *
 */
LOCAL void ca_default_exception_handler(struct exception_handler_args args)
{
	const char *pCtx;

	/*
	 * LOCK around use of sprintf buffer
	 */
	LOCK;
	if (args.chid && args.op != CA_OP_OTHER) {
		sprintf(sprintf_buf, 
			"%s - with request chan=%s op=%ld data type=%s count=%ld", 
			args.ctx,
			ca_name(args.chid),
			args.op,
			dbr_type_to_text(args.type),
			args.count);	 
		pCtx = sprintf_buf;
	}
	else {
		pCtx = args.ctx;
	}
	ca_signal_with_file_and_lineno(args.stat, pCtx, args.pFile, args.lineNo);
	UNLOCK;
}



/*
 *	CA_ADD_FD_REGISTRATION
 *
 *	call their function with their argument whenever 
 *	a new fd is added or removed
 *	(for a manager of the select system call under UNIX)
 *
 */
int epicsShareAPI ca_add_fd_registration(CAFDHANDLER *func, const void *arg)
{
	INITCHK;

	fd_register_func = func;
	fd_register_arg = arg;

  	return ECA_NORMAL;
}



/*
 *	CA_DEFUNCT
 *
 *	what is called by vacated entries in the VMS share image jump table
 *
 */
int ca_defunct()
{
	SEVCHK(ECA_DEFUNCT, NULL);
	return ECA_DEFUNCT;
}


/*
 *	CA_HOST_NAME_FUNCTION()	
 *
 * 	returns a pointer to the channel's host name
 *
 *	currently implemented as a function 
 *	(may be implemented as a MACRO in the future)
 */
READONLY char * epicsShareAPI ca_host_name_function(chid chix)
{
	IIU	*piiu;

	piiu = chix->piiu;

	if(!piiu){
		return "<local host>";
	}
	return piiu->host_name_str;
}


/*
 * ca_v42_ok(chid chan)
 */
int epicsShareAPI ca_v42_ok(chid chan)
{
	int	v42;
	IIU	*piiu;

	piiu = chan->piiu;

	v42 = CA_V42(
		CA_PROTOCOL_VERSION,
		piiu->minor_version_number);

	return v42;
}


/*
 * ca_version()
 * function that returns the CA version string
 */
READONLY char * epicsShareAPI ca_version()
{
	return CA_VERSION_STRING; 
}


/*
 *
 * 	CA_CHANNEL_STATUS
 *
 */
#ifdef vxWorks
int ca_channel_status(int tid)
{
	chid			chix;
	IIU			*piiu;
	struct CA_STATIC	*pcas;

	pcas = (struct CA_STATIC *) 
		taskVarGet(tid, (int *)&ca_static);

	if (pcas == (struct CA_STATIC *) ERROR)
		return ECA_NOCACTX;

#	define ca_static pcas
		LOCK
#	undef ca_static
	piiu = (struct ioc_in_use *) pcas->ca_iiuList.node.next;
	while(piiu){
		chix = (chid) &piiu->chidlist.node;
		while ( (chix = (chid) chix->node.next) ){
			printf(	"%s native type=%d ", 
				ca_name(chix),
				ca_field_type(chix));
			printf(	"N elements=%d IOC=%s state=", 
				ca_element_count(chix),
				piiu->host_name_str);
			switch(ca_state(chix)){
			case cs_never_conn:
				printf("never connected to an IOC");
				break;
			case cs_prev_conn:
				printf("disconnected from IOC");
				break;
			case cs_conn:
				printf("connected to an IOC");
				break;
			case cs_closed:
				printf("invalid channel");
				break;
			default:
				break;
			}
			printf("\n");
		}
		piiu = (struct ioc_in_use *)piiu->node.next;
	}
#	define ca_static pcas
		UNLOCK
#	undef ca_static
	return ECA_NORMAL;
}
#endif /*vxWorks*/


/*
 * ca_replace_printf_handler ()
 */
int epicsShareAPI ca_replace_printf_handler (
int (epicsShareAPIV *ca_printf_func)(const char *pformat, va_list args)
)
{
	INITCHK;

	if (ca_printf_func) {
		ca_static->ca_printf_func = ca_printf_func;
	}
	else {
		/*
		 * os dependent
		 */
		caSetDefaultPrintfHandler();
	}

	return ECA_NORMAL;
}


/*
 *      ca_printf()
 */
int epicsShareAPI ca_printf(char *pformat, ...)
{
	int	( epicsShareAPIV *ca_printf_func )(const char *pformat, va_list args);
	va_list		theArgs;
	int		status;

	va_start(theArgs, pformat);

	ca_printf_func = epicsVprintf;
	if (ca_static) {
		if (ca_static->ca_printf_func) {
			ca_printf_func = ca_static->ca_printf_func;
		}
	}

	status = (*ca_printf_func) (pformat, theArgs);

	va_end(theArgs);

	return status;
}

/*
 * ca_get_field_type()
 */
short epicsShareAPI ca_get_field_type (chid chan) 
{
	if (chan->state==cs_conn) {
		return chan->privType;
	}
	else {
		return TYPENOTCONN;
	}
}

/*
 * ca_get_element_count()
 */
unsigned short epicsShareAPI ca_get_element_count (chid chan) 
{
	if (chan->state==cs_conn) {
		return chan->privCount;
	}
	else {
		return 0;
	}
}

/*
 * ca_get_ioc_connection_count()
 *
 * returns the number of IOC's that CA is connected to
 * (for testing purposes only)
 */
unsigned epicsShareAPI ca_get_ioc_connection_count () 
{
	unsigned count;

	INITCHK;

	LOCK;

	count = ellCount (&ca_static->ca_iiuList);

	/*
	 * if there is a UDP IIU then subtract it out
	 */
	if (ca_static->ca_piiuCast!=NULL) {
		count--;
	}

	UNLOCK;

	return count;
}
