/*************************************************************************\
* 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. 
\*************************************************************************/
/*
 *
 *	share/src/libCom/$Id$	
 *
 *	A file descriptor manager for use with the UNIX system call select
 *
 *	Author	Jeffrey O. Hill
 *		hill@luke.lanl.gov
 *		505 665 1831
 *
 *      Experimental Physics and Industrial Control System (EPICS)
 *
 *	NOTES:
 *
 *	.01 joh 091192	Write fd callbacks are one shots consistent 
 *			with the lower likelyhood of select blocking 
 *			on a fd write.	 
 *
 *	.02 joh 012193	terse DOCUMENTATION has been added to the header file 
 *			share/epicsH/fdmgr.h
 *
 */

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

/*
 * ANSI
 */
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <stdarg.h>

#define select_errno SOCKERRNO 

#ifdef vxWorks
#include <vxWorks.h>
#include <taskLib.h>
#include <sysLib.h>
#include <tickLib.h>
#include <logLib.h>
#include <selectLib.h>
#include <semLib.h>
#endif

#define epicsExportSharedSymbols
#include "epicsAssert.h"
#include "epicsTypes.h"
#include "fdmgr.h"
#include "osiSleep.h"
#include "bsdSocketResource.h"

#ifndef TRUE
#define TRUE 1
#endif
#ifndef FALSE
#define FALSE 0
#endif

#define FDMGR_OK 0
#define FDMGR_ERROR (-1)

#ifndef NULL
#define NULL 0
#endif

#ifndef LOCAL
#define LOCAL static
#endif

#ifndef max
#define max(x, y)  (((x) < (y)) ? (y) : (x))
#endif

typedef struct{
	ELLNODE		node;
	SOCKET		fd;
	enum fdi_type	fdi;		/* the type of fd interest */
	fd_set		*pfds;
	void			(*pfunc)(void *);
	void			*param;
	int			delete_pending;
}fdentry;

typedef struct{
	ELLNODE                 node;
	struct timeval          t;
	void                    (*func)(void *pParam);
	void                    *param;
	enum alarm_list_type    alt;
	unsigned                id;
}fdmgrAlarm;

#if defined(vxWorks)
#	define LOCK(PFDCTX)	assert(semTake((PFDCTX)->lock, WAIT_FOREVER)==FDMGR_OK);
#	define UNLOCK(PFDCTX)	assert(semGive((PFDCTX)->lock)==FDMGR_OK);

#	define UNLOCK_FDMGR_PEND_EVENT(PFDCTX) \
		{(PFDCTX)->fdmgr_pend_event_tid = NULL; \
		assert(semGive((PFDCTX)->fdmgr_pend_event_lock)==FDMGR_OK);}

#	define LOCK_EXPIRED(PFDCTX) \
		assert(semTake((PFDCTX)->expired_alarm_lock, WAIT_FOREVER)==FDMGR_OK);
#	define UNLOCK_EXPIRED(PFDCTX) \
		assert(semGive((PFDCTX)->expired_alarm_lock)==FDMGR_OK);

#	define LOCK_FD_HANDLER(PFDCTX) \
		assert(semTake((PFDCTX)->fd_handler_lock, WAIT_FOREVER)==FDMGR_OK);
#	define UNLOCK_FD_HANDLER(PFDCTX) \
		assert(semGive((PFDCTX)->fd_handler_lock)==FDMGR_OK);

#elif defined(UNIX) || defined(VMS) || defined(_WIN32) || defined(CYGWIN32)
#	define LOCK(PFDCTX)
#	define UNLOCK(PFDCTX)
#	define UNLOCK_FDMGR_PEND_EVENT(PFDCTX) \
		{(PFDCTX)->fdmgr_pend_event_in_use--;}
#	define LOCK_EXPIRED(PFDCTX)
#	define UNLOCK_EXPIRED(PFDCTX)
#	define LOCK_FD_HANDLER(PFDCTX)
#	define UNLOCK_FD_HANDLER(PFDCTX)
#else
#error Please define the host OS type
#endif


#define USEC_PER_SEC 1000000

#define printf @@@@ Please dont use printf in this source @@@@
LOCAL int fdmgrPrintf(char *pformat, ...);

LOCAL int fdmgr_gettimeval(
fdctx           *pfdctx,
struct timeval  *pt
);

LOCAL void select_alarm(void *pParam);

LOCAL int fdmgr_select(
fdctx		*pfdctx,
struct timeval	*ptimeout
);

LOCAL void fdmgr_finish_off_fdentry(
fdctx                   *pfdctx,
register fdentry        *pfdentry
);

LOCAL void lockFDMGRPendEvent (fdctx *pfdctx);

/*
 * This routine is to be only called from fdmgr_pend_event()
 * If other uses are needed then locking issues must be
 * reinvestigated
 */
LOCAL void process_alarm_queue(
	fdctx           *pfdctx,
	struct timeval  *poffset
);


/*
 *
 *	fdmgr_init()
 *
 */
epicsShareFunc fdctx * epicsShareAPI fdmgr_init(void)
{
	fdctx 		*pfdctx;

	assert (bsdSockAttach());

	pfdctx = (fdctx *) calloc(1, sizeof(fdctx));
	if (!pfdctx) {
		bsdSockRelease();
		return pfdctx;
	}

	pfdctx->pAlarmBucket = bucketCreate (1024);
	if (!pfdctx->pAlarmBucket) {
		free (pfdctx);
		bsdSockRelease();
		return NULL;
	}

#	if defined(vxWorks)
		pfdctx->lock = semMCreate (SEM_DELETE_SAFE|SEM_INVERSION_SAFE|SEM_Q_PRIORITY);
		if (pfdctx->lock == NULL){
			bsdSockRelease();
			return NULL;
		}
		pfdctx->fdmgr_pend_event_lock = semMCreate (SEM_DELETE_SAFE|SEM_INVERSION_SAFE|SEM_Q_PRIORITY);
		if (pfdctx->fdmgr_pend_event_lock == NULL){
			bsdSockRelease();
			return NULL;
		}
		pfdctx->expired_alarm_lock = semMCreate (SEM_DELETE_SAFE|SEM_INVERSION_SAFE|SEM_Q_PRIORITY);
		if (pfdctx->expired_alarm_lock == NULL) {
			bsdSockRelease();
			return NULL;
		}
		pfdctx->fd_handler_lock = semMCreate (SEM_DELETE_SAFE|SEM_INVERSION_SAFE|SEM_Q_PRIORITY);
		if (pfdctx->fd_handler_lock == NULL) {
			bsdSockRelease();
			return NULL;
		}
		pfdctx->clk_rate = sysClkRateGet();
		pfdctx->last_tick_count = tickGet();
#	endif

	/*
	 * winsock requires this
	 */
    FD_ZERO (&pfdctx->readch);
    FD_ZERO (&pfdctx->writech);
    FD_ZERO (&pfdctx->excpch);

	ellInit (&pfdctx->fdentry_list);
	ellInit (&pfdctx->fdentry_in_use_list);
	ellInit (&pfdctx->fdentry_free_list);
	ellInit (&pfdctx->alarm_list);
	ellInit (&pfdctx->expired_alarm_list);
	ellInit (&pfdctx->free_alarm_list);

	/*
 	 * returns NULL if unsuccessful
	 */
	return pfdctx;
}


/*
 *
 *	fdmgr_delete()
 *
 */
epicsShareFunc int epicsShareAPI fdmgr_delete(fdctx *pfdctx)
{
	int		status;
	fdmgrAlarm	*palarm;
	fdmgrAlarm	*pnext;

	if(!pfdctx){
		return FDMGR_ERROR;
	}

#	if defined(vxWorks)
		status = semDelete (pfdctx->lock);
		assert (status == FDMGR_OK);
		status = semDelete (pfdctx->fdmgr_pend_event_lock);
		assert (status == FDMGR_OK);
		status = semDelete (pfdctx->expired_alarm_lock);
		assert (status == FDMGR_OK);
		status = semDelete (pfdctx->fd_handler_lock);
		assert (status == FDMGR_OK);
#	endif

	ellFree(&pfdctx->fdentry_list);
	ellFree(&pfdctx->fdentry_in_use_list);
	ellFree(&pfdctx->fdentry_free_list);
	for (	palarm = (fdmgrAlarm *) ellFirst (&pfdctx->alarm_list);
		palarm;
		palarm = pnext) {
		pnext = (fdmgrAlarm *) ellNext (&palarm->node);
		status = bucketRemoveItemUnsignedId(
					pfdctx->pAlarmBucket,
					&palarm->id);
		assert (status == S_bucket_success);
		free (palarm);
	}
	bucketFree (pfdctx->pAlarmBucket);
	ellFree(&pfdctx->expired_alarm_list);
	ellFree(&pfdctx->free_alarm_list);

	bsdSockRelease();

	return FDMGR_OK;
}


/*
 * 	fdmgr_add_timeout()
 */
epicsShareFunc fdmgrAlarmId epicsShareAPI fdmgr_add_timeout(
fdctx 		*pfdctx,
struct timeval 	*ptimeout,
void		(*func)(void *),
void		*param
)
{
	fdmgrAlarm	*palarm=NULL;
	fdmgrAlarm	*pa;
	struct timeval	t;
	int		status;

	if(ptimeout->tv_sec < 0)
		return fdmgrNoAlarm;
	if(ptimeout->tv_usec < 0)
		return fdmgrNoAlarm;

	status = fdmgr_gettimeval(pfdctx, &t);
	if(status < 0)
		return fdmgrNoAlarm;

	LOCK(pfdctx);
	palarm = (fdmgrAlarm *) ellGet(&pfdctx->free_alarm_list);
	UNLOCK(pfdctx);
	if(!palarm){
		palarm = (fdmgrAlarm *) malloc(sizeof(fdmgrAlarm));
		if(!palarm){
			return fdmgrNoAlarm;
		}
	}

	/*
	 * force all fields to a known state
	 */
	memset ((char *)palarm, 0, sizeof(*palarm));

	do {
		pfdctx->nextAlarmId++;
		palarm->id = pfdctx->nextAlarmId;
		status = bucketAddItemUnsignedId (
				pfdctx->pAlarmBucket,
				&palarm->id,
				palarm);
		if (status == S_bucket_noMemory) {
			free (palarm);
			return fdmgrNoAlarm;
		}
	} while (status == S_bucket_idInUse);

	if (status != S_bucket_success) {
		free (palarm);
		errMessage (status, "Alarm installation failed");
		return fdmgrNoAlarm;
	}

	ptimeout->tv_sec += ptimeout->tv_usec/USEC_PER_SEC;
	ptimeout->tv_usec = ptimeout->tv_usec%USEC_PER_SEC;

	palarm->func = func;
	palarm->param = param;
	
	palarm->t.tv_sec = 
		t.tv_sec + ptimeout->tv_sec + 
		(t.tv_usec + ptimeout->tv_usec)/USEC_PER_SEC;

	palarm->t.tv_usec = (t.tv_usec + ptimeout->tv_usec)%USEC_PER_SEC;

	LOCK(pfdctx);
	for(	pa=(fdmgrAlarm *)pfdctx->alarm_list.node.next; 
		pa; 
		pa=(fdmgrAlarm *)pa->node.next){
		if(pa->t.tv_sec == palarm->t.tv_sec){
			if(pa->t.tv_usec >= palarm->t.tv_usec){
				break;
			}
		}
		else if(pa->t.tv_sec > palarm->t.tv_sec){
			break;
		}
	}
	if(pa){
		ellInsert (
			&pfdctx->alarm_list, 
			pa->node.previous, 
			&palarm->node);
	}
	else{
		ellAdd(&pfdctx->alarm_list, &palarm->node);
	}
	palarm->alt = alt_alarm;
	UNLOCK(pfdctx);

	return pfdctx->nextAlarmId;
}


/*
 *	fdmgr_clear_timeout()
 */
epicsShareFunc int epicsShareAPI fdmgr_clear_timeout(
fdctx 		*pfdctx,
fdmgrAlarmId	id	
)
{
	int 			status;
	enum alarm_list_type	alt;
	fdmgrAlarm		*palarm;

	palarm = bucketLookupItemUnsignedId (
				pfdctx->pAlarmBucket,
				&id);
	if (!palarm) {
		return FDMGR_ERROR;
	}

	status = bucketRemoveItemUnsignedId(
				pfdctx->pAlarmBucket,
				&id);
	assert (status == S_bucket_success);

	status = FDMGR_ERROR;

	LOCK(pfdctx);
	alt = palarm->alt;
	if(alt == alt_alarm){
		ellDelete(&pfdctx->alarm_list, &palarm->node);
		ellAdd(&pfdctx->free_alarm_list, &palarm->node);
		palarm->alt = alt_free;
		status = FDMGR_OK;
	}
	else if(alt == alt_expired){
		/*
		 * disable futher use of this alarm
		 */	
		palarm->func = NULL;
	}
	UNLOCK(pfdctx);

	if(alt == alt_expired){

		/*
		 * If we are not called below fdmgr_pend_event()
		 * then wait for completion before returning.
		 * 
		 * waiting for completion of this event routine
		 * when we are in it would cause a deadlock.
		 */
		/*
		 * Wait for it to expire
		 * (and be added to the free list)
		 *
		 * (only applies to multithreaded OS)
		 *
		 * taking the EXPIRED lock forces this routine
		 * to wait until any timer experation
		 * handlers in progress complete
		 *
		 * The primary LOCK is not applied here to 
		 * avoid deadlocking with any fdmgr
		 * routines which might be potentially
		 * called from within an expired alarm 
		 * handler
		 *
		 */
#		ifdef vxWorks
			if(pfdctx->fdmgr_pend_event_tid != taskIdCurrent){
				LOCK_EXPIRED(pfdctx);
				UNLOCK_EXPIRED(pfdctx);
			}
#		endif

		status = FDMGR_OK;
	}

	return status;
}


/*
 *
 *	fdmgr_add_fd()
 *
 *	this routine is supplied solely for compatibility	
 *	with earlier versions of this software
 */
epicsShareFunc int epicsShareAPI fdmgr_add_fd(
fdctx 	*pfdctx,
SOCKET	fd,
void	(*pfunc)(void *),
void	*param
)
{
	int status;

	status = fdmgr_add_callback(
			pfdctx, 
			fd, 
			fdi_read,
			pfunc, 
			param);

	return status;
}


/*
 *
 *	fdmgr_add_fd_callback()
 *
 */
epicsShareFunc int epicsShareAPI fdmgr_add_callback(
fdctx 		*pfdctx,
SOCKET		fd,
enum fdi_type	fdi,
void			(*pfunc)(void *),
void			*param
)
{
	fdentry		*pfdentry;
	fd_set		*pfds;

	if (!FD_IN_FDSET(fd)) {
		fdmgrPrintf ("%s: fd > FD_SETSIZE ignored\n", 
			__FILE__);
		return FDMGR_ERROR;
	}

	switch(fdi){
	case fdi_read:
		pfds = &pfdctx->readch;
		break;
	case fdi_write:
     		pfds = &pfdctx->writech;
		break;
	case fdi_excp:
     		pfds = &pfdctx->excpch;
		break;
	default:
		return FDMGR_ERROR;
	}

	pfdctx->maxfd = max(pfdctx->maxfd, fd+1);
	LOCK(pfdctx);
	pfdentry = (fdentry *) ellGet(&pfdctx->fdentry_free_list);
	UNLOCK(pfdctx);

	if(!pfdentry){
		pfdentry = (fdentry *) malloc(sizeof(fdentry));
		if(!pfdentry){
			return FDMGR_ERROR;
		}
	}

	/*
	 * force all fields to a known state
	 */
	memset((char *)pfdentry, 0, sizeof(*pfdentry));

	pfdentry->fd = fd;
	pfdentry->fdi = fdi;
	pfdentry->pfds = pfds;
	pfdentry->pfunc = pfunc;
	pfdentry->param = param;
	pfdentry->delete_pending = FALSE;

	LOCK(pfdctx);
	ellAdd(&pfdctx->fdentry_list, &pfdentry->node);
	UNLOCK(pfdctx);

	return FDMGR_OK;
}


/*
 *
 *	fdmgr_clear_fd()
 *
 *	included solely for compatibility with previous release
 *
 */
epicsShareFunc int epicsShareAPI fdmgr_clear_fd(
fdctx 	*pfdctx,
SOCKET	fd 
)
{
	return fdmgr_clear_callback(pfdctx, fd, fdi_read);
}


/*
 *
 *	fdmgr_clear_callback()
 *
 */
epicsShareFunc int epicsShareAPI fdmgr_clear_callback(
fdctx 		*pfdctx,
SOCKET		fd,
enum fdi_type	fdi
)
{
	register fdentry	*pfdentry;
	int			status;
	int			delete_pending;

	delete_pending = FALSE;
	status = FDMGR_ERROR;

	LOCK(pfdctx);
	for(	pfdentry = (fdentry *) pfdctx->fdentry_list.node.next;
		pfdentry;
		pfdentry = (fdentry *) pfdentry->node.next){

		if(pfdentry->fd == fd && pfdentry->fdi == fdi){
			ellDelete(&pfdctx->fdentry_list, &pfdentry->node);
			fdmgr_finish_off_fdentry(pfdctx, pfdentry);
			status = FDMGR_OK;
			break;
		}
	}

	/*
	 * also check entries which are currently in use
	 * and therefore undeletable until after the
	 * fdmgr gets thru fdmgr_pend_event
	 */
        for(    pfdentry = (fdentry *) pfdctx->fdentry_in_use_list.node.next;
                pfdentry;
                pfdentry = (fdentry *) pfdentry->node.next){

		if(pfdentry->fd == fd && pfdentry->fdi == fdi){
			delete_pending = TRUE;
			pfdentry->delete_pending = TRUE;
                        status = FDMGR_OK;
                        break;
                }
        }
	UNLOCK(pfdctx);
	
	/*
	 * wait for it to finish if it is in progress
	 * when running in a multithreaded environment
	 *
	 * Taking the lock here guarantees that if the 
	 * event we are deleting is in progress then
	 * we will wait for it to complete prior to
	 * proceeding
	 */
#	ifdef vxWorks
		if(delete_pending == TRUE){
			LOCK_FD_HANDLER(pfdctx);
			UNLOCK_FD_HANDLER(pfdctx);
		}
#	endif

	/*
	 * If it is an ukn fd its a problem worth printing out
	 */
	if(status != FDMGR_OK){
		fdmgrPrintf("fdmg: delete of ukn fd failed\n");
	}

	return status;
}


/*
 *
 *	fdmgr_finish_off_fdentry()
 *
 *	!! LOCK(pfdctx) must be applied !!
 *
 */
LOCAL void fdmgr_finish_off_fdentry(
fdctx 			*pfdctx,
register fdentry	*pfdentry
)
{
	FD_CLR(pfdentry->fd, pfdentry->pfds);
	ellAdd(&pfdctx->fdentry_free_list, &pfdentry->node);
}


/*
 *
 *	fdmgr_pend_event()
 *
 */
epicsShareFunc int epicsShareAPI fdmgr_pend_event(
fdctx 				*pfdctx,
struct timeval 			*ptimeout 
)
{
	int			status;
	struct timeval		t;
	fdmgrAlarmId		alarmId;


	lockFDMGRPendEvent(pfdctx);

	/*
	 * If its a poll dont add a timeout
	 * (and more delays) 
	 */
	if(ptimeout->tv_sec == 0 && ptimeout->tv_usec == 0){
		pfdctx->select_tmo = TRUE;
		t.tv_sec = 0;
		t.tv_usec = 0;	
		/*
		 * silence gcc warnings
		 */
		alarmId = fdmgrNoAlarm;
	}
	else{
		pfdctx->select_tmo = FALSE;
		alarmId = fdmgr_add_timeout(
				pfdctx, 
				ptimeout, 
				select_alarm,
				pfdctx);
		if (alarmId==fdmgrNoAlarm) {
			return FDMGR_ERROR;
		}
		process_alarm_queue(pfdctx, &t);
	}

	while (TRUE) {
		status = fdmgr_select(pfdctx, &t);
		process_alarm_queue(pfdctx, &t);
		if(status){
			break;
		}
		if(pfdctx->select_tmo)
			break;
	}

	if(pfdctx->select_tmo==FALSE && alarmId != fdmgrNoAlarm)
		fdmgr_clear_timeout(pfdctx, alarmId);

	UNLOCK_FDMGR_PEND_EVENT(pfdctx);

	return FDMGR_OK;
}



/*
 *
 *	fdmgr_select()
 *
 *	returns TRUE if any labor was performed, otherwise FALSE
 *
 */
LOCAL int fdmgr_select(
fdctx 				*pfdctx,
struct timeval 			*ptimeout
)
{
	register fdentry *pfdentry;
	int labor_performed;
	int status;
	int ioPending = 0;

	labor_performed = FALSE;

	LOCK(pfdctx);
	for(	pfdentry = (fdentry *) pfdctx->fdentry_list.node.next;
		pfdentry;
		pfdentry = (fdentry *) pfdentry->node.next){

     		FD_SET(pfdentry->fd, pfdentry->pfds);
			ioPending = 1;
	}
	UNLOCK(pfdctx);

	/*
 	 * win32 requires this (others will
	 * run faster with this installed)
	 */
	if (!ioPending) {
		/*
		 * recover from subtle differences between
		 * windows sockets and UNIX sockets implementation
		 * of select()
		 */
		if (ptimeout->tv_sec!=0 ||
			ptimeout->tv_usec!=0 ) {
			osiSleep (ptimeout->tv_sec, ptimeout->tv_usec);
		}
		status = 0;
	}
	else {
		/*
		 * V5 vxWorks ref man has an ominous
		 * comment about deleting a task while
		 * it is in select() so I am turning
		 * on task delete disable to be safe
		 */
#		ifdef vxWorks 
			taskSafe();
#		endif
  		status = select(
				pfdctx->maxfd,
				&pfdctx->readch,
				&pfdctx->writech,
				&pfdctx->excpch,
				ptimeout);

#		ifdef vxWorks 
			taskUnsafe();
#		endif
	}

	if(status == 0){
		return labor_performed;
	}
	else if(status < 0){
		if(SOCKERRNO == SOCK_EINTR)
			;
		else if(SOCKERRNO == SOCK_EINVAL)
			fdmgrPrintf(	
				"fdmgr: bad select args ? %d %d %d\n",
				pfdctx->maxfd,
				ptimeout->tv_sec,
				ptimeout->tv_usec);
		else
			fdmgrPrintf(	
				"fdmgr: error from select %d=%s\n",
				SOCKERRNO, SOCKERRSTR);

		return labor_performed;
	}

	pfdentry = (fdentry *) &pfdctx->fdentry_list.node;
	while(TRUE){

		LOCK(pfdctx)
		pfdentry = (fdentry *) pfdentry->node.next;
		if(pfdentry){
			ellDelete(&pfdctx->fdentry_list, &pfdentry->node);
			/*
			 *
			 * holding place where it can be marked 
			 * pending delete but not deleted
 			 *
			 */
			ellAdd(&pfdctx->fdentry_in_use_list, &pfdentry->node);
		}
		UNLOCK(pfdctx)
	
		if(!pfdentry){
			break;
		}

		/*
		 * check for fd active
		 */
		if(FD_ISSET(pfdentry->fd, pfdentry->pfds)){
     			FD_CLR(pfdentry->fd, pfdentry->pfds);

			/*
			 * sync with clear
			 *
			 * This allows the clearing thread to wait 
			 * until the event that it is clearing completes
			 */
			LOCK_FD_HANDLER(pfdctx);

			/*
			 * Dont execute if they have cleared
			 * it
			 */
			if(!pfdentry->delete_pending){
     				(*pfdentry->pfunc)(pfdentry->param);
				/*
				 * writes are one shots
				 */
				if (pfdentry->fdi==fdi_write) {
					pfdentry->delete_pending = TRUE;
				}
				labor_performed = TRUE;
			}
			UNLOCK_FD_HANDLER(pfdctx);
		}

		LOCK(pfdctx)
		ellDelete(&pfdctx->fdentry_in_use_list, &pfdentry->node);

		/*
		 * if it is marked pending delete
		 * reset it and place it on the free list	
		 */
		if(pfdentry->delete_pending){
			fdmgr_finish_off_fdentry(pfdctx, pfdentry);
		}
		else{
			ellAdd(&pfdctx->fdentry_list, &pfdentry->node);
		}
		UNLOCK(pfdctx)

	}

	return labor_performed;
}



/*
 *
 *	process_alarm_queue()
 *
 *	only to be called by fdmgr_pend_event(). If other uses
 *	come up then the locking must be revisited
 */
LOCAL void process_alarm_queue(
fdctx 		*pfdctx,
struct timeval	*poffset
)
{
	struct timeval	t;
	int		status;
	fdmgrAlarm	*pa;
	fdmgrAlarm	*nextpa;

	status = fdmgr_gettimeval(pfdctx, &t);
	assert (status >= 0);

	LOCK(pfdctx);
	for(pa = (fdmgrAlarm*)pfdctx->alarm_list.node.next; pa; pa = nextpa){
		if(pa->t.tv_sec > t.tv_sec)
			break;
		else if(pa->t.tv_sec == t.tv_sec)
			if(pa->t.tv_usec > t.tv_usec)
				break;

		nextpa = (fdmgrAlarm*) pa->node.next;
		ellDelete(&pfdctx->alarm_list, &pa->node);
		ellAdd(&pfdctx->expired_alarm_list, &pa->node);
		pa->alt = alt_expired;
		status = bucketRemoveItemUnsignedId(
					pfdctx->pAlarmBucket,
					&pa->id);
		assert (status == S_bucket_success);
	}
	UNLOCK(pfdctx);

	/*
	 * Done this way so I am not traversing the alarm que while
	 * they are in an alarm handler (and potentially modifying
 	 * the alarm queue).
	 */
	/*
	 * I dont want the primary LOCK to be applied while in their
 	 * alarm handler as this would prevent them from
	 * calling fdmgr routines from within a handler
	 */
	/*
	 * applying the expired lock here synchronizes
	 * properly with the clear event routine
	 * on multithreaded OS
	 */
	LOCK_EXPIRED(pfdctx);
	pa = (fdmgrAlarm*) pfdctx->expired_alarm_list.node.next;
	while(pa){
		void	(*pfunc)(void *pParam);

		/*
		 * check to see if it has been disabled 
		 * by a clear timer call
		 */
		pfunc = pa->func;
		if(pfunc){
			(*pfunc)(pa->param);
		}
		pa = (fdmgrAlarm*)pa->node.next;
	}
	UNLOCK_EXPIRED(pfdctx);

	/*
	 * mark it as a member of the free list while the 
	 * LOCK is applied and then force the entire
	 * expired list onto the free list
	 */
	LOCK(pfdctx);
	pa = (fdmgrAlarm*) pfdctx->expired_alarm_list.node.next;
	while(pa){
		pa->alt = alt_free;
		pa = (fdmgrAlarm *) pa->node.next;
	}
	ellConcat(&pfdctx->free_alarm_list, &pfdctx->expired_alarm_list);

	pa = (fdmgrAlarm *)pfdctx->alarm_list.node.next;
	if(pa){
		if(pa->t.tv_usec >= t.tv_usec){
			poffset->tv_sec = pa->t.tv_sec - t.tv_sec;
			poffset->tv_usec = pa->t.tv_usec - t.tv_usec;
		}else{
			/* else borrow */
			poffset->tv_sec = pa->t.tv_sec - t.tv_sec - 1;
			poffset->tv_usec = pa->t.tv_usec 
				+ USEC_PER_SEC - t.tv_usec;
		}
	}else{
		poffset->tv_sec = 0;
		poffset->tv_usec = 0;
	}
	UNLOCK(pfdctx);
}


/*
 *
 *	select_alarm()
 *
 */
LOCAL void select_alarm(void *pParam)
{
	fdctx 	*pfdctx = pParam;

	pfdctx->select_tmo = TRUE;
}




/*
 *
 * UNIX & VMS
 * fdmgr_gettimeval
 *
 */
#if defined(UNIX) || defined(VMS) || defined(CYGWIN32)
LOCAL int fdmgr_gettimeval(
fdctx 		*pfdctx,
struct timeval	*pt 
)
{
	return gettimeofday (pt, (struct timezone *) NULL);
}
#endif


/*
 *
 * WIN32
 * fdmgr_gettimeval
 *
 *
 */
#ifdef _WIN32
LOCAL int fdmgr_gettimeval(
fdctx           *pfdctx,
struct timeval  *pt
)
{
        SYSTEMTIME st;
        GetSystemTime(&st);
        pt->tv_sec = (long)st.wSecond + (long)st.wMinute*60 + (long)st.wHour*3600;
        pt->tv_usec = st.wMilliseconds*1000;

        return 0;
}
#endif


/*
 * vxWorks
 * fdmgr_gettimeval
 *
 */
#ifdef vxWorks
LOCAL int fdmgr_gettimeval(
fdctx 		*pfdctx,
struct timeval	*pt
)
{
	unsigned long		current;

	LOCK(pfdctx);
	current = tickGet();
	if(current<pfdctx->last_tick_count){
		pfdctx->sec_offset += (~(unsigned long)0)/pfdctx->clk_rate;
	}
	pfdctx->last_tick_count = current;

	pt->tv_sec = current/pfdctx->clk_rate;
	pt->tv_sec += pfdctx->sec_offset;
	pt->tv_usec = ((current%pfdctx->clk_rate)*
			USEC_PER_SEC)/pfdctx->clk_rate;
	UNLOCK(pfdctx);

	return FDMGR_OK;
}
#endif


/*
 * lockFDMGRPendEvent()
 */
LOCAL void lockFDMGRPendEvent (fdctx *pfdctx)
{
#	if defined(vxWorks)
		assert(semTake (pfdctx->fdmgr_pend_event_lock, WAIT_FOREVER)==FDMGR_OK); 
		pfdctx->fdmgr_pend_event_tid = taskIdCurrent;
#	else
		assert (pfdctx->fdmgr_pend_event_in_use==0); 
		pfdctx->fdmgr_pend_event_in_use++; 
#	endif
}


/*
 *
 *
 *      fdmgrPrintf()
 *
 *      Dump error messages to the appropriate place
 *
 */
LOCAL int fdmgrPrintf(char *pformat, ...)
{
        va_list         args;
        int             status;

        va_start(args, pformat);

#ifndef vxWorks
        status = vfprintf(
                        stderr,
                        pformat,
                        args);
#else /*vxWorks*/
        {
                int     logMsgArgs[6];
                int     i;

                for(i=0; i< NELEMENTS(logMsgArgs); i++){
                        logMsgArgs[i] = va_arg(args, int);
                }

                status = logMsg(
                                pformat,
                                logMsgArgs[0],
                                logMsgArgs[1],
                                logMsgArgs[2],
                                logMsgArgs[3],
                                logMsgArgs[4],
                                logMsgArgs[5]);

        }
#endif /*vxWorks*/

        va_end(args);

        return status;
}

