diff options
Diffstat (limited to 'sendmail/libsm/clock.c')
-rw-r--r-- | sendmail/libsm/clock.c | 640 |
1 files changed, 640 insertions, 0 deletions
diff --git a/sendmail/libsm/clock.c b/sendmail/libsm/clock.c new file mode 100644 index 0000000..1bdb4fd --- /dev/null +++ b/sendmail/libsm/clock.c @@ -0,0 +1,640 @@ +/* + * 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 */ + + +/* +** 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 */ + int wasblocked; + + /* negative times are not allowed */ + 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 /* 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(); + if (wasblocked == 0) + (void) sm_releasesignal(SIGALRM); + return NULL; + } + else + { + ev = SmFreeEventList; + SmFreeEventList = ev->ev_link; + } + LEAVE_CRITICAL(); + + /* insert new event */ + 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 /* 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); + 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; + int wasblocked; +# if SM_CONF_SETITIMER + struct itimerval clr; +# endif /* SM_CONF_SETITIMER */ + + if (ev == NULL) + return; + + /* find the parent event */ + wasblocked = sm_blocksignal(SIGALRM); + 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 */ + 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 */ + } +} +/* +** 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 */ + int wasblocked; + + /* 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; + + wasblocked = sm_blocksignal(SIGALRM); + + /* 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(); + + /* restore clocks and pick up anything spare */ + if (wasblocked == 0) + (void) sm_releasesignal(SIGALRM); +} +/* +** 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; + kill(mypid, sig); + } + +#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. +*/ + + +# 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 */ + |