Logo Search packages:      
Sourcecode: batv-milter version File versions  Download package

clock.c

/*
 * Copyright (c) 1998-2004 Sendmail, Inc. and its suppliers.
 *    All rights reserved.
 * Copyright (c) 1983, 1995-1997 Eric P. Allman.  All rights reserved.
 * Copyright (c) 1988, 1993
 *    The Regents of the University of California.  All rights reserved.
 *
 * By using this file, you agree to the terms and conditions set
 * forth in the LICENSE file which can be found at the top level of
 * the sendmail distribution.
 *
 */

#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 /* SM_CONF_SETITIMER */
#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 /* _FFR_SLEEP_USE_SELECT > 0 */
#if defined(_FFR_MAX_SLEEP_TIME) && _FFR_MAX_SLEEP_TIME > 2
# include <syslog.h>
#endif /* defined(_FFR_MAX_SLEEP_TIME) && _FFR_MAX_SLEEP_TIME > 2 */

#ifndef sigmask
# define sigmask(s)     (1 << ((s) - 1))
#endif /* ! sigmask */

#ifdef WIN32
static void (*alarm_handler)(int);
#endif /* WIN32 */

/*
**  SM_SETEVENTM -- set an event to happen at a specific time in milliseconds.
**
**    Events are stored in a sorted list for fast processing.
**    An event only applies to the process that set it.
**    Source is #ifdef'd to work with older OS's that don't have setitimer()
**    (that is, don't have a timer granularity less than 1 second).
**
**    Parameters:
**          intvl -- interval until next event occurs (milliseconds).
**          func -- function to call on event.
**          arg -- argument to func on event.
**
**    Returns:
**          On success returns the SM_EVENT entry created.
**          On failure returns NULL.
**
**    Side Effects:
**          none.
*/

static SM_EVENT   *volatile SmEventQueue;       /* head of event queue */
static SM_EVENT   *volatile SmFreeEventList;    /* list of free events */

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);
}

/*
**    NOTE: THIS CAN BE CALLED FROM A SIGNAL HANDLER.  DO NOT ADD
**          ANYTHING TO THIS ROUTINE UNLESS YOU KNOW WHAT YOU ARE
**          DOING.
*/

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 /*  SM_CONF_SETITIMER */
      auto time_t now, nowi;
#endif /*  SM_CONF_SETITIMER */
#ifndef WIN32
      int wasblocked;
#endif /* ! WIN32 */

      /* negative times are not allowed */
      if (intvl <= 0)
            return NULL;

#ifndef WIN32
      wasblocked = sm_blocksignal(SIGALRM);
#else /* ! WIN32 */
      alarm_handler = NULL;
#endif /* ! WIN32 */
#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 /*  SM_CONF_SETITIMER */
      now = time(NULL);
      nowi = now + (time_t)(intvl / 1000);
#endif /*  SM_CONF_SETITIMER */

      /* search event queue for correct position */
      for (evp = (SM_EVENT **) (&SmEventQueue);
           (ev = *evp) != NULL;
           evp = &ev->ev_link)
      {
#if SM_CONF_SETITIMER
            if (timercmp(&(ev->ev_time), &nowi, >=))
#else /* SM_CONF_SETITIMER */
            if (ev->ev_time >= nowi)
#endif /* SM_CONF_SETITIMER */
                  break;
      }

      ENTER_CRITICAL();
      if (SmFreeEventList == NULL)
      {
            /*
            **  This shouldn't happen.  If called from sm_seteventm(),
            **  we have just malloced a SmFreeEventList entry.  If
            **  called from a signal handler, it should have been
            **  from an existing event which sm_tick() just added to
            **  SmFreeEventList.
            */

            LEAVE_CRITICAL();
#ifndef WIN32
            if (wasblocked == 0)
                  (void) sm_releasesignal(SIGALRM);
#endif /* ! WIN32 */
            return NULL;
      }
      else
      {
            ev = SmFreeEventList;
            SmFreeEventList = ev->ev_link;
      }
      LEAVE_CRITICAL();

      /* insert new event */
      ev->ev_time = nowi;
#ifdef WIN32
      ev->ev_func = (void (__cdecl *)(int))func; /* even at W1 !!! */
#else /* WIN32 */
      ev->ev_func = func;
#endif /* WIN32 */
      ev->ev_arg = arg;
      ev->ev_pid = getpid();
      ENTER_CRITICAL();
      ev->ev_link = *evp;
      *evp = ev;
      LEAVE_CRITICAL();

#ifndef WIN32
      (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 /* SM_CONF_SETITIMER */
      intvl = SmEventQueue->ev_time - now;
      (void) alarm((unsigned) (intvl < 1 ? 1 : intvl));
# endif /* SM_CONF_SETITIMER */
      if (wasblocked == 0)
            (void) sm_releasesignal(SIGALRM);
#endif /* ! WIN32 */
      return ev;
}
/*
**  SM_CLREVENT -- remove an event from the event queue.
**
**    Parameters:
**          ev -- pointer to event to remove.
**
**    Returns:
**          none.
**
**    Side Effects:
**          arranges for event ev to not happen.
*/

void
sm_clrevent(ev)
      register SM_EVENT *ev;
{
      register SM_EVENT **evp;
#ifndef WIN32
      int wasblocked;
# if SM_CONF_SETITIMER
      struct itimerval clr;
# endif /* SM_CONF_SETITIMER */
#endif /* ! WIN32 */

      if (ev == NULL)
            return;

      /* find the parent event */
#ifndef WIN32
      wasblocked = sm_blocksignal(SIGALRM);
#else /* ! WIN32 */
      alarm_handler = NULL;
#endif /* ! WIN32 */
      for (evp = (SM_EVENT **) (&SmEventQueue);
           *evp != NULL;
           evp = &(*evp)->ev_link)
      {
            if (*evp == ev)
                  break;
      }

      /* now remove it */
      if (*evp != NULL)
      {
            ENTER_CRITICAL();
            *evp = ev->ev_link;
            ev->ev_link = SmFreeEventList;
            SmFreeEventList = ev;
            LEAVE_CRITICAL();
      }

      /* restore clocks and pick up anything spare */
#ifndef WIN32
      if (wasblocked == 0)
            (void) sm_releasesignal(SIGALRM);
      if (SmEventQueue != NULL)
            (void) kill(getpid(), SIGALRM);
      else
      {
            /* nothing left in event queue, no need for an alarm */
# 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 /* SM_CONF_SETITIMER */
            (void) alarm(0);
# endif /* SM_CONF_SETITIMER */
      }
#endif /* ! WIN32 */
}
/*
**  SM_CLEAR_EVENTS -- remove all events from the event queue.
**
**    Parameters:
**          none.
**
**    Returns:
**          none.
*/

void
sm_clear_events()
{
      register SM_EVENT *ev;
#if SM_CONF_SETITIMER
      struct itimerval clr;
#endif /* SM_CONF_SETITIMER */
#ifndef WIN32
      int wasblocked;
#endif /* ! WIN32 */

      /* nothing will be left in event queue, no need for an alarm */
#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 /* SM_CONF_SETITIMER */
      (void) alarm(0);
#endif /* SM_CONF_SETITIMER */

      if (SmEventQueue == NULL)
            return;

#ifndef WIN32
      wasblocked = sm_blocksignal(SIGALRM);
#endif /* ! WIN32 */

      /* find the end of the EventQueue */
      for (ev = SmEventQueue; ev->ev_link != NULL; ev = ev->ev_link)
            continue;

      ENTER_CRITICAL();
      ev->ev_link = SmFreeEventList;
      SmFreeEventList = SmEventQueue;
      SmEventQueue = NULL;
      LEAVE_CRITICAL();

#ifndef WIN32
      /* restore clocks and pick up anything spare */
      if (wasblocked == 0)
            (void) sm_releasesignal(SIGALRM);
#endif /* ! WIN32 */
}
/*
**  SM_TICK -- take a clock tick
**
**    Called by the alarm clock.  This routine runs events as needed.
**    Always called as a signal handler, so we assume that SIGALRM
**    has been blocked.
**
**    Parameters:
**          One that is ignored; for compatibility with signal handlers.
**
**    Returns:
**          none.
**
**    Side Effects:
**          calls the next function in EventQueue.
**
**    NOTE: THIS CAN BE CALLED FROM A SIGNAL HANDLER.  DO NOT ADD
**          ANYTHING TO THIS ROUTINE UNLESS YOU KNOW WHAT YOU ARE
**          DOING.
*/

/* ARGSUSED */
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 /* SM_CONF_SETITIMER */
      register time_t now;
#endif /* SM_CONF_SETITIMER */

#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 /* SM_CONF_SETITIMER */
      (void) alarm(0);
      now = time(NULL);
#endif /* SM_CONF_SETITIMER */

      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
            {
                  /* If we get here, we are in trouble */
                  abort();
            }
            PendingSignal &= ~sigbit;
#ifndef WIN32
            kill(mypid, sig);
#endif /* ! WIN32 */
      }

#if SM_CONF_SETITIMER
      gettimeofday(&now, NULL);
#else /* SM_CONF_SETITIMER */
      now = time(NULL);
#endif /* SM_CONF_SETITIMER */
      while ((ev = SmEventQueue) != NULL &&
             (ev->ev_pid != mypid ||
#if SM_CONF_SETITIMER
            timercmp(&ev->ev_time, &now, <=)
#else /* SM_CONF_SETITIMER */
            ev->ev_time <= now
#endif /* SM_CONF_SETITIMER */
            ))
      {
            void (*f)__P((int));
            int arg;
            pid_t pid;

            /* process the event on the top of the queue */
            ev = SmEventQueue;
            SmEventQueue = SmEventQueue->ev_link;

            /* we must be careful in here because ev_func may not return */
            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 /* SM_CONF_SETITIMER */
                  if (SmEventQueue->ev_time > now)
                        (void) alarm((unsigned) (SmEventQueue->ev_time
                                           - now));
                  else
                        (void) alarm(3);
#endif /* SM_CONF_SETITIMER */
            }

            /* call ev_func */
            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 /* SM_CONF_SETITIMER */
            (void) alarm(0);
            now = time(NULL);
#endif /* SM_CONF_SETITIMER */
      }
      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 /* SM_CONF_SETITIMER */
            (void) alarm((unsigned) (SmEventQueue->ev_time - now));
#endif /* SM_CONF_SETITIMER */
      }
      errno = save_errno;
      return SIGFUNC_RETURN;
}
/*
**  SLEEP -- a version of sleep that works with this stuff
**
**    Because Unix sleep uses the alarm facility, I must reimplement
**    it here.
**
**    Parameters:
**          intvl -- time to sleep.
**
**    Returns:
**          zero.
**
**    Side Effects:
**          waits for intvl time.  However, other events can
**          be run during that interval.
*/

#ifndef WIN32

# if !HAVE_NANOSLEEP
static void sm_endsleep __P((int));
static bool volatile SmSleepDone;
# endif /* !HAVE_NANOSLEEP */

#ifndef SLEEP_T
# define SLEEP_T  unsigned int
#endif /* ! SLEEP_T */

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 /* HAVE_NANOSLEEP */
      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 /* _FFR_SLEEP_USE_SELECT > 0 */
#endif /* _FFR_SLEEP_USE_SELECT > 0 */
#if SM_CONF_SETITIMER
      struct timeval now, begin, diff;
# if _FFR_SLEEP_USE_SELECT > 0
      struct timeval slpv;
# endif /* _FFR_SLEEP_USE_SELECT > 0 */
#else /*  SM_CONF_SETITIMER */
      time_t begin, now;
#endif /*  SM_CONF_SETITIMER */

      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 /* 0 */
            intvl = _FFR_MAX_SLEEP_TIME;
      }
#endif /* defined(_FFR_MAX_SLEEP_TIME) && _FFR_MAX_SLEEP_TIME > 2 */
      SmSleepDone = false;

#if SM_CONF_SETITIMER
# if _FFR_SLEEP_USE_SELECT > 0
      slpv.tv_sec = intvl;
      slpv.tv_usec = 0;
# endif /* _FFR_SLEEP_USE_SELECT > 0 */
      (void) gettimeofday(&now, NULL);
      begin = now;
#else /*  SM_CONF_SETITIMER */
      now = begin = time(NULL);
#endif /*  SM_CONF_SETITIMER */

      ev = sm_setevent((time_t) intvl, sm_endsleep, 0);
      if (ev == NULL)
      {
            /* COMPLAIN */
#if 0
            syslog(LOG_ERR, "sleep: sm_setevent(%u) failed", intvl);
#endif /* 0 */
            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 /* _FFR_SLEEP_USE_SELECT > 0 */
#else /* SM_CONF_SETITIMER */
            now = time(NULL);

            /*
            **  Check whether time expired before signal is released.
            **  Due to the granularity of time() add 1 to be on the
            **  safe side.
            */

            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 /* _FFR_SLEEP_USE_SELECT > 0 */
#endif /* SM_CONF_SETITIMER */
#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 /* _FFR_SLEEP_USE_SELECT > 0 */
            (void) pause();
      }

      /* if out of the loop without the event being triggered remove it */
      if (!SmSleepDone)
            sm_clrevent(ev);
      if (was_held > 0)
            (void) sm_blocksignal(SIGALRM);
      return (SLEEP_T) 0;
#endif /* HAVE_NANOSLEEP */
}

#if !HAVE_NANOSLEEP
static void
sm_endsleep(ignore)
      int ignore;
{
      /*
      **  NOTE: THIS CAN BE CALLED FROM A SIGNAL HANDLER.  DO NOT ADD
      **    ANYTHING TO THIS ROUTINE UNLESS YOU KNOW WHAT YOU ARE
      **    DOING.
      */

      SmSleepDone = true;
}
#endif /* !HAVE_NANOSLEEP */

#endif /* ! WIN32 */

Generated by  Doxygen 1.6.0   Back to index