/*****************************************************************************

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 <esr@thyrsus.com>, 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.

******************************************************************************/
#include "fetchmail.h"
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>
#include <errno.h>

#ifndef TRUE
#define TRUE 1
#define FALSE 0
#endif

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
 * alarm after the required interval and will then generate alarms
 * seconds until it is certain that the critical section (ie., the window)
 * is exited.
 */
static sig_atomic_t	alarm_latch = FALSE;

void gotsigalrm(int sig)
{
    set_signal_handler(sig, gotsigalrm);
    lastsig = sig;
    alarm_latch = TRUE;
}
#endif /* SLEEP_WITH_ALARM */

int interruptible_idle(int seconds)
/* time for a pause in the action; return TRUE if awakened by signal */
{
    int awoken = FALSE;

#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
     * <francis@netscape.com> 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;
    set_signal_handler(SIGALRM, gotsigalrm);	/* first trap signals */
    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  */
    /* caught by periodic alarms (see it_interval) */
    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 */
    set_signal_handler(SIGALRM, SIG_IGN);
    }
#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;

    timeout.tv_sec = seconds;
    timeout.tv_usec = 0;
    do {
	lastsig = 0;
	select(0,0,0,0, &timeout);
    } while (lastsig == SIGCHLD);
    }
#endif

    if (lastsig == SIGUSR1 || ((seconds && getuid() == ROOT_UID)
	&& lastsig == SIGHUP))
       awoken = TRUE;

    /* now lock out interrupts again */
    set_signal_handler(SIGUSR1, SIG_IGN);
    if (getuid() == ROOT_UID)
	set_signal_handler(SIGHUP, SIG_IGN);

    return(awoken ? lastsig : 0);
}

#ifdef MAIN
int main(int argc, char **argv)
{
    for (;;)
    {
	printf("How may I serve you, master?\n");
	interruptible_idle(5);
    }
}
#endif /* MAIN */

/* idle.c ends here */
