/*
 *	Network Queueing System (NQS)
 *  This version of NQS is Copyright (C) 1992  John Roman
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 1, or (at your option)
 *  any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program; if not, write to the Free Software
 *  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 */
/*++ nqs_vtimer.c - Network Queueing System
 *
 * $Source: /usr2/jrroma/nqs/nqs-3.30/src/RCS/nqs_vtimer.c,v $
 *
 * DESCRIPTION:
 *
 *	This module contains the two public functions of:
 *
 *		nqs_vtimer(), and
 *		nqs_valarm()
 *
 *	which respectively set virtual timers, and enable virtual
 *	timers.  Nqs_valarm() also catches the SIGALRM signals
 *	used to implement the virtual timer mechanism.
 *
 *
 *	Author:
 *	-------
 *	Brent A. Kingsbury, Sterling Software Incorporated.
 *	August 12, 1985.
 *
 *
 * STANDARDS VIOLATIONS:
 *   None.
 *
 * REVISION HISTORY: ($Revision: 1.4 $ $Date: 1992/12/22 15:41:40 $ $State: Exp $)
 * $Log: nqs_vtimer.c,v $
 * Revision 1.4  1992/12/22  15:41:40  jrroma
 * Version 3.30
 *
 * Revision 1.3  92/06/18  17:31:33  jrroma
 * Added gnu header
 * 
 * Revision 1.2  92/02/12  14:06:38  jrroma
 * Fixed declaration of static routines.
 * 
 * Revision 1.1  92/02/12  14:05:40  jrroma
 * Initial revision
 * 
 *
 */

#include <signal.h>
#include "nqs.h"
#include "nqsxvars.h"		/* Global vars */
#include <errno.h>
#include <time.h>

#ifndef __CEXTRACT__
#if __STDC__

static int setalarm ( void );

#else /* __STDC__ */

static int setalarm (/* void */);

#endif /* __STDC__ */
#endif /* __CEXTRACT__ */


#define	MAX_ALARMS	4	/* Max number of alarms handled */
				/* 1 for -a request activation; */
				/* 1 for pipe queue expiration; */
				/* 1 for pre-arrive expiration; */
				/* 1 for nqs_aboque.c; */


/*
 *	Variables local to this module.
 */
static short n_vtimers = 0;	/* Number of alarms in effect */
static struct {
	time_t vtime;		/* GMT time at which fn should be called */
	void (*fn_to_call)();	/* Function to call */
} vtimer [MAX_ALARMS];


/*** nqs_vtimer
 *
 *
 *	void nqs_vtimer():
 *
 *	Set a "virtual timer" at which time the specified function
 *	is to be called with no arguments.  The timer set by this call
 *	however will NOT be enabled until an nqs_valarm() call is made.
 */
void nqs_vtimer (ptrvtime, fn_to_call)
time_t *ptrvtime;			/* GMT time at which fn should go */
void (*fn_to_call)();			/* Function to call when time is up */
{
	register int i;			/* Index var */
	register int vtime;		/* GMT time at which fn should go */

	vtime = *ptrvtime;		/* Get time */
	i = 0;
	while (i < n_vtimers && vtimer [i].fn_to_call != fn_to_call) {
		i++;
	}
	if (i >= n_vtimers) {
		/*
		 *  The specified function to be activated is not
		 *  already listed in the call table.
		 */
		if (n_vtimers >= MAX_ALARMS) {
			/*
			 *  Cannot set alarm.  Table is full.
			 */
			printf ("F$Virtual timer table full ");
			printf ("in call to to nqs_vtimer().\n");
	 		errno = 0;		/* Not a system call error */
			nqs_abort();		/* Crash */
		}
		else {
			/*
			 *  Add this function to the table.
			 */
			vtimer [n_vtimers].vtime = vtime;
			vtimer [n_vtimers].fn_to_call = fn_to_call;
			n_vtimers++;		/* One more timer going */
		}
	}
	else {
		/*
		 *  The specified function was already in the table
		 *  to be activated at the proper time.
		 */
		if (vtimer [i].vtime > vtime) vtimer [i].vtime = vtime;
	}
}


/*** nqs_valarm
 *
 *
 *	nqs_valarm():
 *
 *	Enable ALL virtual timers as set by the nqs_vtimer() call and
 *	also catch a SIGALRM signal to implement the virtual timer schema.
 */
void nqs_valarm ()
{
	register int i;
	void (*fn)();
	register time_t timenow;	/* Current GMT time */

	signal (SIGALRM, nqs_valarm);	/* Re-instate SIGALRM handler */
	if (n_vtimers) {		/* Timer(s) were set */
		/*
		 *  Loop until all remaining timers are set to
		 *  go off in the future.
		 */
		do {
			i = 0;
			timenow = time ((time_t *)0);
			/*
			 *  Loop to service timers which have gone off.
			 */
			while (i < n_vtimers) {
				if (vtimer [i].vtime > timenow) {
					/*
					 *  This timer has not gone off.
					 */
					i++;	/* Examine next timer */
					continue;
				}
				/*
				 *  This timer has gone off.
				 */
				fn = vtimer [i].fn_to_call;
				n_vtimers--;
				if (i < n_vtimers) {
					/*
					 *  Another timer follows the
					 *  one that just went off.
					 *  Copy the succeeding timer
					 *  state into the one being
					 *  deleted.
					 */
					vtimer [i].vtime
						= vtimer [n_vtimers].vtime;
					vtimer [i].fn_to_call
						= vtimer [n_vtimers].fn_to_call;
				}
				(*fn)();/* Call the function */
			}
		} while (setalarm() == -1);
	}
}


/*** setalarm
 *
 *
 *	int setalarm():
 *	Set next closest alarm.
 *
 *	Returns:
 *		0: if an alarm was successfully set (or no timers
 *		   existed);
 *	       -1: if a timer exists which has gone off and needs to
 *		   be handled immediately.
 */
static int setalarm ()
{
	register int i;			/* Index */
	register time_t min_time;
	time_t timenow;

	if (n_vtimers) {
		/*
		 *  Now, search for the alarm that should go off the
		 *  soonest.
		 */
		time (&timenow);	/* Get current time */
		min_time = vtimer [0].vtime;
		for (i=1; i < n_vtimers; i++) {
			if (vtimer [i].vtime < min_time) {
				min_time = vtimer [i].vtime;
			}
		}
		if (min_time <= timenow) {
			/*
			 *  We have a timer that should go off NOW.
			 */
			return (-1);		/* No timer set */
		}
		min_time -= timenow;		/* Get alarm duration */
		if (min_time > 32767) {
			/*
			 *  On some machines, integers are 16-bits.  NQS
			 *  may find itself scheduling a timer that is
			 *  several days in duration.  That many seconds
			 *  cannot be represented in a 16-bit integer.
			 *  For such alarms, we set as many smaller
			 *  alarms as necessary to reach the proper
			 *  time.
			 */
			min_time = 32767;
		}
		alarm ((unsigned) min_time);	/* Set the alarm */
	}
	return (0);				/* Success */
}
