/***********************************************************************
* *
* This software is part of the ast package *
* Copyright (c) 1982-2010 AT&T Intellectual Property *
* and is licensed under the *
* Common Public License, Version 1.0 *
* by AT&T Intellectual Property *
* *
* A copy of the License is available at *
* http://www.opensource.org/licenses/cpl1.0.txt *
* (with md5 checksum 059e8cd6165cb4c31e351f2b69388fd9) *
* *
* Information and Software Systems Research *
* AT&T Research *
* Florham Park NJ *
* *
* David Korn <dgk@research.att.com> *
* *
***********************************************************************/
#pragma prototyped
#include <ast.h>
#include <sig.h>
#include <error.h>
#include "fault.h"
#include "defs.h"
#include "FEATURE/sigfeatures"
#include "FEATURE/time"
typedef struct _timer
{
double wakeup;
double incr;
struct _timer *next;
void (*action)(void*);
void *handle;
} Timer_t;
#define IN_ADDTIMEOUT 1
#define IN_SIGALRM 2
#define DEFER_SIGALRM 4
#define SIGALRM_CALL 8
static Timer_t *tptop, *tpmin, *tpfree;
static char time_state;
static double getnow(void)
{
register double now;
#ifdef timeofday
struct timeval tp;
timeofday(&tp);
now = tp.tv_sec + 1.e-6*tp.tv_usec;
#else
now = (double)time((time_t*)0);
#endif /* timeofday */
return(now+.001);
}
/*
* set an alarm for <t> seconds
*/
static double setalarm(register double t)
{
#if defined(_lib_setitimer) && defined(ITIMER_REAL)
struct itimerval tnew, told;
tnew.it_value.tv_sec = t;
tnew.it_value.tv_usec = 1.e6*(t- (double)tnew.it_value.tv_sec);
if(t && tnew.it_value.tv_sec==0 && tnew.it_value.tv_usec<1000)
tnew.it_value.tv_usec = 1000;
tnew.it_interval.tv_sec = 0;
tnew.it_interval.tv_usec = 0;
if(setitimer(ITIMER_REAL,&tnew,&told) < 0)
errormsg(SH_DICT,ERROR_system(1),e_alarm);
t = told.it_value.tv_sec + 1.e-6*told.it_value.tv_usec;
#else
unsigned seconds = (unsigned)t;
if(t && seconds<1)
seconds=1;
t = (double)alarm(seconds);
#endif
return(t);
}
/* signal handler for alarm call */
static void sigalrm(int sig)
{
register Timer_t *tp, *tplast, *tpold, *tpnext;
double now;
static double left;
NOT_USED(sig);
left = 0;
if(time_state&SIGALRM_CALL)
time_state &= ~SIGALRM_CALL;
else if(alarm(0))
sh_fault(SIGALRM|SH_TRAP);
if(time_state)
{
if(time_state&IN_ADDTIMEOUT)
time_state |= DEFER_SIGALRM;
errno = EINTR;
return;
}
time_state |= IN_SIGALRM;
sigrelease(SIGALRM);
while(1)
{
now = getnow();
tpold = tpmin = 0;
for(tplast=0,tp=tptop; tp; tp=tpnext)
{
tpnext = tp->next;
if(tp->action)
{
if(tp->wakeup <=now)
{
if(!tpold || tpold->wakeup>tp->wakeup)
tpold = tp;
}
else
{
if(!tpmin || tpmin->wakeup>tp->wakeup)
tpmin=tp;
}
tplast = tp;
}
else
{
if(tplast)
tplast->next = tp->next;
else
tptop = tp->next;
tp->next = tpfree;
tpfree = tp;
}
}
if((tp=tpold) && tp->incr)
{
while((tp->wakeup += tp->incr) <= now);
if(!tpmin || tpmin->wakeup>tp->wakeup)
tpmin=tp;
}
if(tpmin && (left==0 || (tp && tpmin->wakeup < (now+left))))
{
if(left==0)
signal(SIGALRM,sigalrm);
left = setalarm(tpmin->wakeup-now);
if(left && (now+left) < tpmin->wakeup)
setalarm(left);
else
left=tpmin->wakeup-now;
}
if(tp)
{
void (*action)(void*);
action = tp->action;
if(!tp->incr)
tp->action = 0;
errno = EINTR;
time_state &= ~IN_SIGALRM;
(*action)(tp->handle);
time_state |= IN_SIGALRM;
}
else
break;
}
if(!tpmin)
signal(SIGALRM,(sh.sigflag[SIGALRM]&SH_SIGFAULT)?sh_fault:SIG_DFL);
time_state &= ~IN_SIGALRM;
errno = EINTR;
}
static void oldalrm(void *handle)
{
Handler_t fn = *(Handler_t*)handle;
free(handle);
(*fn)(SIGALRM);
}
void *sh_timeradd(unsigned long msec,int flags,void (*action)(void*),void *handle)
{
register Timer_t *tp;
double t;
Handler_t fn;
t = ((double)msec)/1000.;
if(t<=0 || !action)
return((void*)0);
if(tp=tpfree)
tpfree = tp->next;
else if(!(tp=(Timer_t*)malloc(sizeof(Timer_t))))
return((void*)0);
tp->wakeup = getnow() + t;
tp->incr = (flags?t:0);
tp->action = action;
tp->handle = handle;
time_state |= IN_ADDTIMEOUT;
tp->next = tptop;
tptop = tp;
if(!tpmin || tp->wakeup < tpmin->wakeup)
{
tpmin = tp;
fn = (Handler_t)signal(SIGALRM,sigalrm);
if((t= setalarm(t))>0 && fn && fn!=(Handler_t)sigalrm)
{
Handler_t *hp = (Handler_t*)malloc(sizeof(Handler_t));
if(hp)
{
*hp = fn;
sh_timeradd((long)(1000*t), 0, oldalrm, (void*)hp);
}
}
tp = tptop;
}
else if(tpmin && !tpmin->action)
time_state |= DEFER_SIGALRM;
time_state &= ~IN_ADDTIMEOUT;
if(time_state&DEFER_SIGALRM)
{
time_state=SIGALRM_CALL;
sigalrm(SIGALRM);
if(tp!=tptop)
tp=0;
}
return((void*)tp);
}
/*
* delete timer <tp>. If <tp> is NULL, all timers are deleted
*/
void timerdel(void *handle)
{
register Timer_t *tp = (Timer_t*)handle;
if(tp)
tp->action = 0;
else
{
for(tp=tptop; tp; tp=tp->next)
tp->action = 0;
if(tpmin)
{
tpmin = 0;
setalarm((double)0);
}
signal(SIGALRM,(sh.sigflag[SIGALRM]&SH_SIGFAULT)?sh_fault:SIG_DFL);
}
}