idle.c 5.5 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
/*****************************************************************************

NAME:
   idle.c -- code for interruptible delays without sleep(3).

ENTRY POINTS:
   interruptible_idle() -- delay for some time, interruptible by signal.

THEORY:
   Sometimes you need more than one time delay per program, so alarm(3)
won't cut it.  This code illustrates time delays with select(2).

AUTHOR:
   Eric S. Raymond <[email protected]>, 1997.  This source code example
is part of fetchmail and the Unix Cookbook, and are released under the
MIT license.  Compile with -DMAIN to build the demonstrator.
Eric S. Raymond's avatar
Eric S. Raymond committed
17

18
******************************************************************************/
Matthias Andree's avatar
Matthias Andree committed
19
#include "fetchmail.h"
Eric S. Raymond's avatar
Eric S. Raymond committed
20 21 22 23 24 25
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>
#include <errno.h>

26 27 28 29
#ifndef TRUE
#define TRUE 1
#define FALSE 0
#endif
Eric S. Raymond's avatar
Eric S. Raymond committed
30 31 32 33 34 35 36 37 38

volatile int lastsig;		/* last signal received */

#ifdef SLEEP_WITH_ALARM
/*
 * The function of this variable is to remove the window during which a
 * SIGALRM can hose the code (ALARM is triggered *before* pause() is called).
 * This is a bit of a kluge; the real right thing would use sigprocmask(),
 * sigsuspend().  This workaround lets the interval timer trigger the first
39 40 41
 * alarm after the required interval and will then generate alarms
 * seconds until it is certain that the critical section (ie., the window)
 * is exited.
Eric S. Raymond's avatar
Eric S. Raymond committed
42 43 44
 */
static sig_atomic_t	alarm_latch = FALSE;

Eric S. Raymond's avatar
Eric S. Raymond committed
45
RETSIGTYPE gotsigalrm(int sig)
Eric S. Raymond's avatar
Eric S. Raymond committed
46
{
Eric S. Raymond's avatar
Eric S. Raymond committed
47
    set_signal_handler(sig, gotsigalrm);
Eric S. Raymond's avatar
Eric S. Raymond committed
48 49 50 51 52 53 54 55 56 57 58 59 60
    lastsig = sig;
    alarm_latch = TRUE;
}
#endif /* SLEEP_WITH_ALARM */

#ifdef __EMX__
/* Various EMX-specific definitions */
static int itimerflag;

void itimerthread(void* dummy)
{
    if (outlevel >= O_VERBOSE)
	report(stderr, 
Eric S. Raymond's avatar
Eric S. Raymond committed
61
	       GT_("fetchmail: thread sleeping for %d sec.\n"), poll_interval);
Eric S. Raymond's avatar
Eric S. Raymond committed
62 63 64 65 66 67 68 69
    while(1)
    {
	_sleep2(poll_interval*1000);
	kill((getpid()), SIGALRM);
    }
}
#endif

Eric S. Raymond's avatar
Eric S. Raymond committed
70
int interruptible_idle(int seconds)
Eric S. Raymond's avatar
Eric S. Raymond committed
71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111
/* time for a pause in the action; return TRUE if awakened by signal */
{
    int awoken = FALSE;

#ifndef __EMX__
#ifdef SLEEP_WITH_ALARM		/* not normally on */
    /*
     * We can't use sleep(3) here because we need an alarm(3)
     * equivalent in order to implement server nonresponse timeout.
     * We'll just assume setitimer(2) is available since fetchmail
     * has to have a BSDoid socket layer to work at all.
     */
    /* 
     * This code stopped working under glibc-2, apparently due
     * to the change in signal(2) semantics.  (The siginterrupt
     * line, added later, should fix this problem.) John Stracke
     * <[email protected]> wrote:
     *
     * The problem seems to be that, after hitting the interval
     * timer while talking to the server, the process no longer
     * responds to SIGALRM.  I put in printf()s to see when it
     * reached the pause() for the poll interval, and I checked
     * the return from setitimer(), and everything seemed to be
     * working fine, except that the pause() just ignored SIGALRM.
     * I thought maybe the itimer wasn't being fired, so I hit
     * it with a SIGALRM from the command line, and it ignored
     * that, too.  SIGUSR1 woke it up just fine, and it proceeded
     * to repoll--but, when the dummy server didn't respond, it
     * never timed out, and SIGALRM wouldn't make it.
     *
     * (continued below...)
     */
    {
    struct itimerval ntimeout;

    ntimeout.it_interval.tv_sec = 5; /* repeat alarm every 5 secs */
    ntimeout.it_interval.tv_usec = 0;
    ntimeout.it_value.tv_sec  = seconds;
    ntimeout.it_value.tv_usec = 0;

    alarm_latch = FALSE;
Eric S. Raymond's avatar
Eric S. Raymond committed
112
    set_signal_handler(SIGALRM, gotsigalrm);	/* first trap signals */
Eric S. Raymond's avatar
Eric S. Raymond committed
113 114 115
    setitimer(ITIMER_REAL,&ntimeout,NULL);	/* then start timer */
    /* there is a very small window between the next two lines */
    /* which could result in a deadlock.  But this will now be  */
116
    /* caught by periodic alarms (see it_interval) */
Eric S. Raymond's avatar
Eric S. Raymond committed
117 118 119 120 121 122
    if (!alarm_latch)
	pause();
    /* stop timer */
    ntimeout.it_interval.tv_sec = ntimeout.it_interval.tv_usec = 0;
    ntimeout.it_value.tv_sec  = ntimeout.it_value.tv_usec = 0;
    setitimer(ITIMER_REAL,&ntimeout,NULL);	/* now stop timer */
Eric S. Raymond's avatar
Eric S. Raymond committed
123
    set_signal_handler(SIGALRM, SIG_IGN);
Eric S. Raymond's avatar
Eric S. Raymond committed
124 125 126 127 128 129 130 131 132 133 134 135 136 137 138
    }
#else
    /* 
     * So the workaround I used is to make it sleep by using
     * select() instead of setitimer()/pause().  select() is
     * perfectly happy being called with a timeout and
     * no file descriptors; it just sleeps until it hits the
     * timeout.  The only concern I had was that it might
     * implement its timeout with SIGALRM--there are some
     * Unices where this is done, because select() is a library
     * function--but apparently not.
     */
    {
    struct timeval timeout;

139
    timeout.tv_sec = seconds;
Eric S. Raymond's avatar
Eric S. Raymond committed
140 141 142 143 144 145 146 147 148
    timeout.tv_usec = 0;
    do {
	lastsig = 0;
	select(0,0,0,0, &timeout);
    } while (lastsig == SIGCHLD);
    }
#endif
#else /* EMX */
    alarm_latch = FALSE;
Eric S. Raymond's avatar
Eric S. Raymond committed
149
    set_signal_handler(SIGALRM, gotsigalrm);
Eric S. Raymond's avatar
Eric S. Raymond committed
150 151 152 153
    _beginthread(itimerthread, NULL, 32768, NULL);
    /* see similar code above */
    if (!alarm_latch)
	pause();
Eric S. Raymond's avatar
Eric S. Raymond committed
154
    set_signal_handler(SIGALRM, SIG_IGN);
Eric S. Raymond's avatar
Eric S. Raymond committed
155
#endif /* ! EMX */
Eric S. Raymond's avatar
Eric S. Raymond committed
156 157
    if (lastsig == SIGUSR1 || ((seconds && getuid() == ROOT_UID)
	&& lastsig == SIGHUP))
Eric S. Raymond's avatar
Eric S. Raymond committed
158 159 160
       awoken = TRUE;

    /* now lock out interrupts again */
Eric S. Raymond's avatar
Eric S. Raymond committed
161
    set_signal_handler(SIGUSR1, SIG_IGN);
Eric S. Raymond's avatar
Eric S. Raymond committed
162
    if (getuid() == ROOT_UID)
Eric S. Raymond's avatar
Eric S. Raymond committed
163
	set_signal_handler(SIGHUP, SIG_IGN);
Eric S. Raymond's avatar
Eric S. Raymond committed
164 165 166 167

    return(awoken ? lastsig : 0);
}

168
#ifdef MAIN
Eric S. Raymond's avatar
Eric S. Raymond committed
169
int main(int argc, char **argv)
170 171 172 173 174 175 176 177 178
{
    for (;;)
    {
	printf("How may I serve you, master?\n");
	interruptible_idle(5);
    }
}
#endif /* MAIN */

Eric S. Raymond's avatar
Eric S. Raymond committed
179
/* idle.c ends here */