#include <sm/gen.h>
SM_RCSID("@(#)$Id: clock.c,v 1.47 2005/06/14 23:07:20 ca Exp $")
#include <unistd.h>
#include <time.h>
#include <errno.h>
#if SM_CONF_SETITIMER
# include <sm/time.h>
#endif
#include <sm/heap.h>
#include <sm/debug.h>
#include <sm/bitops.h>
#include <sm/clock.h>
#include "local.h"
#if _FFR_SLEEP_USE_SELECT > 0
# include <sys/types.h>
#endif
#if defined(_FFR_MAX_SLEEP_TIME) && _FFR_MAX_SLEEP_TIME > 2
# include <syslog.h>
#endif
#ifndef sigmask
# define sigmask(s) (1 << ((s) - 1))
#endif
static SM_EVENT *volatile SmEventQueue;
static SM_EVENT *volatile SmFreeEventList;
SM_EVENT *
sm_seteventm(intvl, func, arg)
int intvl;
void (*func)__P((int));
int arg;
{
ENTER_CRITICAL();
if (SmFreeEventList == NULL)
{
SmFreeEventList = (SM_EVENT *) sm_pmalloc_x(sizeof *SmFreeEventList);
SmFreeEventList->ev_link = NULL;
}
LEAVE_CRITICAL();
return sm_sigsafe_seteventm(intvl, func, arg);
}
SM_EVENT *
sm_sigsafe_seteventm(intvl, func, arg)
int intvl;
void (*func)__P((int));
int arg;
{
register SM_EVENT **evp;
register SM_EVENT *ev;
#if SM_CONF_SETITIMER
auto struct timeval now, nowi, ival;
auto struct itimerval itime;
#else
auto time_t now, nowi;
#endif
int wasblocked;
if (intvl <= 0)
return NULL;
wasblocked = sm_blocksignal(SIGALRM);
#if SM_CONF_SETITIMER
ival.tv_sec = intvl / 1000;
ival.tv_usec = (intvl - ival.tv_sec * 1000) * 10;
(void) gettimeofday(&now, NULL);
nowi = now;
timeradd(&now, &ival, &nowi);
#else
now = time(NULL);
nowi = now + (time_t)(intvl / 1000);
#endif
for (evp = (SM_EVENT **) (&SmEventQueue);
(ev = *evp) != NULL;
evp = &ev->ev_link)
{
#if SM_CONF_SETITIMER
if (timercmp(&(ev->ev_time), &nowi, >=))
#else
if (ev->ev_time >= nowi)
#endif
break;
}
ENTER_CRITICAL();
if (SmFreeEventList == NULL)
{
LEAVE_CRITICAL();
if (wasblocked == 0)
(void) sm_releasesignal(SIGALRM);
return NULL;
}
else
{
ev = SmFreeEventList;
SmFreeEventList = ev->ev_link;
}
LEAVE_CRITICAL();
ev->ev_time = nowi;
ev->ev_func = func;
ev->ev_arg = arg;
ev->ev_pid = getpid();
ENTER_CRITICAL();
ev->ev_link = *evp;
*evp = ev;
LEAVE_CRITICAL();
(void) sm_signal(SIGALRM, sm_tick);
# if SM_CONF_SETITIMER
timersub(&SmEventQueue->ev_time, &now, &itime.it_value);
itime.it_interval.tv_sec = 0;
itime.it_interval.tv_usec = 0;
if (itime.it_value.tv_sec < 0)
itime.it_value.tv_sec = 0;
if (itime.it_value.tv_sec == 0 && itime.it_value.tv_usec == 0)
itime.it_value.tv_usec = 1000;
(void) setitimer(ITIMER_REAL, &itime, NULL);
# else
intvl = SmEventQueue->ev_time - now;
(void) alarm((unsigned) (intvl < 1 ? 1 : intvl));
# endif
if (wasblocked == 0)
(void) sm_releasesignal(SIGALRM);
return ev;
}
void
sm_clrevent(ev)
register SM_EVENT *ev;
{
register SM_EVENT **evp;
int wasblocked;
# if SM_CONF_SETITIMER
struct itimerval clr;
# endif
if (ev == NULL)
return;
wasblocked = sm_blocksignal(SIGALRM);
for (evp = (SM_EVENT **) (&SmEventQueue);
*evp != NULL;
evp = &(*evp)->ev_link)
{
if (*evp == ev)
break;
}
if (*evp != NULL)
{
ENTER_CRITICAL();
*evp = ev->ev_link;
ev->ev_link = SmFreeEventList;
SmFreeEventList = ev;
LEAVE_CRITICAL();
}
if (wasblocked == 0)
(void) sm_releasesignal(SIGALRM);
if (SmEventQueue != NULL)
(void) kill(getpid(), SIGALRM);
else
{
# if SM_CONF_SETITIMER
clr.it_interval.tv_sec = 0;
clr.it_interval.tv_usec = 0;
clr.it_value.tv_sec = 0;
clr.it_value.tv_usec = 0;
(void) setitimer(ITIMER_REAL, &clr, NULL);
# else
(void) alarm(0);
# endif
}
}
void
sm_clear_events()
{
register SM_EVENT *ev;
#if SM_CONF_SETITIMER
struct itimerval clr;
#endif
int wasblocked;
#if SM_CONF_SETITIMER
clr.it_interval.tv_sec = 0;
clr.it_interval.tv_usec = 0;
clr.it_value.tv_sec = 0;
clr.it_value.tv_usec = 0;
(void) setitimer(ITIMER_REAL, &clr, NULL);
#else
(void) alarm(0);
#endif
if (SmEventQueue == NULL)
return;
wasblocked = sm_blocksignal(SIGALRM);
for (ev = SmEventQueue; ev->ev_link != NULL; ev = ev->ev_link)
continue;
ENTER_CRITICAL();
ev->ev_link = SmFreeEventList;
SmFreeEventList = SmEventQueue;
SmEventQueue = NULL;
LEAVE_CRITICAL();
if (wasblocked == 0)
(void) sm_releasesignal(SIGALRM);
}
SIGFUNC_DECL
sm_tick(sig)
int sig;
{
register SM_EVENT *ev;
pid_t mypid;
int save_errno = errno;
#if SM_CONF_SETITIMER
struct itimerval clr;
struct timeval now;
#else
register time_t now;
#endif
#if SM_CONF_SETITIMER
clr.it_interval.tv_sec = 0;
clr.it_interval.tv_usec = 0;
clr.it_value.tv_sec = 0;
clr.it_value.tv_usec = 0;
(void) setitimer(ITIMER_REAL, &clr, NULL);
gettimeofday(&now, NULL);
#else
(void) alarm(0);
now = time(NULL);
#endif
FIX_SYSV_SIGNAL(sig, sm_tick);
errno = save_errno;
CHECK_CRITICAL(sig);
mypid = getpid();
while (PendingSignal != 0)
{
int sigbit = 0;
int sig = 0;
if (bitset(PEND_SIGHUP, PendingSignal))
{
sigbit = PEND_SIGHUP;
sig = SIGHUP;
}
else if (bitset(PEND_SIGINT, PendingSignal))
{
sigbit = PEND_SIGINT;
sig = SIGINT;
}
else if (bitset(PEND_SIGTERM, PendingSignal))
{
sigbit = PEND_SIGTERM;
sig = SIGTERM;
}
else if (bitset(PEND_SIGUSR1, PendingSignal))
{
sigbit = PEND_SIGUSR1;
sig = SIGUSR1;
}
else
{
abort();
}
PendingSignal &= ~sigbit;
kill(mypid, sig);
}
#if SM_CONF_SETITIMER
gettimeofday(&now, NULL);
#else
now = time(NULL);
#endif
while ((ev = SmEventQueue) != NULL &&
(ev->ev_pid != mypid ||
#if SM_CONF_SETITIMER
timercmp(&ev->ev_time, &now, <=)
#else
ev->ev_time <= now
#endif
))
{
void (*f)__P((int));
int arg;
pid_t pid;
ev = SmEventQueue;
SmEventQueue = SmEventQueue->ev_link;
f = ev->ev_func;
arg = ev->ev_arg;
pid = ev->ev_pid;
ENTER_CRITICAL();
ev->ev_link = SmFreeEventList;
SmFreeEventList = ev;
LEAVE_CRITICAL();
if (pid != getpid())
continue;
if (SmEventQueue != NULL)
{
#if SM_CONF_SETITIMER
if (timercmp(&SmEventQueue->ev_time, &now, >))
{
timersub(&SmEventQueue->ev_time, &now,
&clr.it_value);
clr.it_interval.tv_sec = 0;
clr.it_interval.tv_usec = 0;
if (clr.it_value.tv_sec < 0)
clr.it_value.tv_sec = 0;
if (clr.it_value.tv_sec == 0 &&
clr.it_value.tv_usec == 0)
clr.it_value.tv_usec = 1000;
(void) setitimer(ITIMER_REAL, &clr, NULL);
}
else
{
clr.it_interval.tv_sec = 0;
clr.it_interval.tv_usec = 0;
clr.it_value.tv_sec = 3;
clr.it_value.tv_usec = 0;
(void) setitimer(ITIMER_REAL, &clr, NULL);
}
#else
if (SmEventQueue->ev_time > now)
(void) alarm((unsigned) (SmEventQueue->ev_time
- now));
else
(void) alarm(3);
#endif
}
errno = save_errno;
(*f)(arg);
#if SM_CONF_SETITIMER
clr.it_interval.tv_sec = 0;
clr.it_interval.tv_usec = 0;
clr.it_value.tv_sec = 0;
clr.it_value.tv_usec = 0;
(void) setitimer(ITIMER_REAL, &clr, NULL);
gettimeofday(&now, NULL);
#else
(void) alarm(0);
now = time(NULL);
#endif
}
if (SmEventQueue != NULL)
{
#if SM_CONF_SETITIMER
timersub(&SmEventQueue->ev_time, &now, &clr.it_value);
clr.it_interval.tv_sec = 0;
clr.it_interval.tv_usec = 0;
if (clr.it_value.tv_sec < 0)
clr.it_value.tv_sec = 0;
if (clr.it_value.tv_sec == 0 && clr.it_value.tv_usec == 0)
clr.it_value.tv_usec = 1000;
(void) setitimer(ITIMER_REAL, &clr, NULL);
#else
(void) alarm((unsigned) (SmEventQueue->ev_time - now));
#endif
}
errno = save_errno;
return SIGFUNC_RETURN;
}
# if !HAVE_NANOSLEEP
static void sm_endsleep __P((int));
static bool volatile SmSleepDone;
# endif
#ifndef SLEEP_T
# define SLEEP_T unsigned int
#endif
SLEEP_T
sleep(intvl)
unsigned int intvl;
{
#if HAVE_NANOSLEEP
struct timespec rqtp;
if (intvl == 0)
return (SLEEP_T) 0;
rqtp.tv_sec = intvl;
rqtp.tv_nsec = 0;
nanosleep(&rqtp, NULL);
return (SLEEP_T) 0;
#else
int was_held;
SM_EVENT *ev;
#if _FFR_SLEEP_USE_SELECT > 0
int r;
# if _FFR_SLEEP_USE_SELECT > 0
struct timeval sm_io_to;
# endif
#endif
#if SM_CONF_SETITIMER
struct timeval now, begin, diff;
# if _FFR_SLEEP_USE_SELECT > 0
struct timeval slpv;
# endif
#else
time_t begin, now;
#endif
if (intvl == 0)
return (SLEEP_T) 0;
#if defined(_FFR_MAX_SLEEP_TIME) && _FFR_MAX_SLEEP_TIME > 2
if (intvl > _FFR_MAX_SLEEP_TIME)
{
syslog(LOG_ERR, "sleep: interval=%u exceeds max value %d",
intvl, _FFR_MAX_SLEEP_TIME);
# if 0
SM_ASSERT(intvl < (unsigned int) INT_MAX);
# endif
intvl = _FFR_MAX_SLEEP_TIME;
}
#endif
SmSleepDone = false;
#if SM_CONF_SETITIMER
# if _FFR_SLEEP_USE_SELECT > 0
slpv.tv_sec = intvl;
slpv.tv_usec = 0;
# endif
(void) gettimeofday(&now, NULL);
begin = now;
#else
now = begin = time(NULL);
#endif
ev = sm_setevent((time_t) intvl, sm_endsleep, 0);
if (ev == NULL)
{
#if 0
syslog(LOG_ERR, "sleep: sm_setevent(%u) failed", intvl);
#endif
SmSleepDone = true;
}
was_held = sm_releasesignal(SIGALRM);
while (!SmSleepDone)
{
#if SM_CONF_SETITIMER
(void) gettimeofday(&now, NULL);
timersub(&now, &begin, &diff);
if (diff.tv_sec < 0 ||
(diff.tv_sec == 0 && diff.tv_usec == 0))
break;
# if _FFR_SLEEP_USE_SELECT > 0
timersub(&slpv, &diff, &sm_io_to);
# endif
#else
now = time(NULL);
if (!(begin + (time_t) intvl + 1 > now))
break;
# if _FFR_SLEEP_USE_SELECT > 0
sm_io_to.tv_sec = intvl - (now - begin);
if (sm_io_to.tv_sec <= 0)
sm_io_to.tv_sec = 1;
sm_io_to.tv_usec = 0;
# endif
#endif
#if _FFR_SLEEP_USE_SELECT > 0
if (intvl <= _FFR_SLEEP_USE_SELECT)
{
r = select(0, NULL, NULL, NULL, &sm_io_to);
if (r == 0)
break;
}
else
#endif
(void) pause();
}
if (!SmSleepDone)
sm_clrevent(ev);
if (was_held > 0)
(void) sm_blocksignal(SIGALRM);
return (SLEEP_T) 0;
#endif
}
#if !HAVE_NANOSLEEP
static void
sm_endsleep(ignore)
int ignore;
{
SmSleepDone = true;
}
#endif