1
/*****************************************************************************
2
3
NAME:
4
   idle.c -- code for interruptible delays without sleep(3).
5
6
ENTRY POINTS:
7
   interruptible_idle() -- delay for some time, interruptible by signal.
8
9
THEORY:
10
   Sometimes you need more than one time delay per program, so alarm(3)
11
won't cut it.  This code illustrates time delays with select(2).
12
13
AUTHOR:
14
   Eric S. Raymond <esr@thyrsus.com>, 1997.  This source code example
15
is part of fetchmail and the Unix Cookbook, and are released under the
16
MIT license.  Compile with -DMAIN to build the demonstrator.
17
18
******************************************************************************/
19
#include <stdio.h>
20
#include <stdlib.h>
21
#include <unistd.h>
22
#include <signal.h>
23
#include <errno.h>
24
#include <fetchmail.h>	/* for ROOT_UID */
25
26
#ifndef TRUE
27
#define TRUE 1
28
#define FALSE 0
29
#endif
30
31
volatile int lastsig;		/* last signal received */
32
33
#ifdef SLEEP_WITH_ALARM
34
/*
35
 * The function of this variable is to remove the window during which a
36
 * SIGALRM can hose the code (ALARM is triggered *before* pause() is called).
37
 * This is a bit of a kluge; the real right thing would use sigprocmask(),
38
 * sigsuspend().  This workaround lets the interval timer trigger the first
39
 * alarm after the required interval and will then generate alarms
40
 * seconds until it is certain that the critical section (ie., the window)
41
 * is exited.
42
 */
43
static sig_atomic_t	alarm_latch = FALSE;
44
45
RETSIGTYPE gotsigalrm(int sig)
46
{
47
    set_signal_handler(sig, gotsigalrm);
48
    lastsig = sig;
49
    alarm_latch = TRUE;
50
}
51
#endif /* SLEEP_WITH_ALARM */
52
53
#ifdef __EMX__
54
/* Various EMX-specific definitions */
55
static int itimerflag;
56
57
void itimerthread(void* dummy)
58
{
59
    if (outlevel >= O_VERBOSE)
60
	report(stderr, 
61
	       GT_("fetchmail: thread sleeping for %d sec.\n"), poll_interval);
62
    while(1)
63
    {
64
	_sleep2(poll_interval*1000);
65
	kill((getpid()), SIGALRM);
66
    }
67
}
68
#endif
69
70
int interruptible_idle(int seconds)
71
/* time for a pause in the action; return TRUE if awakened by signal */
72
{
73
    int awoken = FALSE;
74
75
#ifndef __EMX__
76
#ifdef SLEEP_WITH_ALARM		/* not normally on */
77
    /*
78
     * We can't use sleep(3) here because we need an alarm(3)
79
     * equivalent in order to implement server nonresponse timeout.
80
     * We'll just assume setitimer(2) is available since fetchmail
81
     * has to have a BSDoid socket layer to work at all.
82
     */
83
    /* 
84
     * This code stopped working under glibc-2, apparently due
85
     * to the change in signal(2) semantics.  (The siginterrupt
86
     * line, added later, should fix this problem.) John Stracke
87
     * <francis@netscape.com> wrote:
88
     *
89
     * The problem seems to be that, after hitting the interval
90
     * timer while talking to the server, the process no longer
91
     * responds to SIGALRM.  I put in printf()s to see when it
92
     * reached the pause() for the poll interval, and I checked
93
     * the return from setitimer(), and everything seemed to be
94
     * working fine, except that the pause() just ignored SIGALRM.
95
     * I thought maybe the itimer wasn't being fired, so I hit
96
     * it with a SIGALRM from the command line, and it ignored
97
     * that, too.  SIGUSR1 woke it up just fine, and it proceeded
98
     * to repoll--but, when the dummy server didn't respond, it
99
     * never timed out, and SIGALRM wouldn't make it.
100
     *
101
     * (continued below...)
102
     */
103
    {
104
    struct itimerval ntimeout;
105
106
    ntimeout.it_interval.tv_sec = 5; /* repeat alarm every 5 secs */
107
    ntimeout.it_interval.tv_usec = 0;
108
    ntimeout.it_value.tv_sec  = seconds;
109
    ntimeout.it_value.tv_usec = 0;
110
111
    alarm_latch = FALSE;
112
    set_signal_handler(SIGALRM, gotsigalrm);	/* first trap signals */
113
    setitimer(ITIMER_REAL,&ntimeout,NULL);	/* then start timer */
114
    /* there is a very small window between the next two lines */
115
    /* which could result in a deadlock.  But this will now be  */
116
    /* caught by periodic alarms (see it_interval) */
117
    if (!alarm_latch)
118
	pause();
119
    /* stop timer */
120
    ntimeout.it_interval.tv_sec = ntimeout.it_interval.tv_usec = 0;
121
    ntimeout.it_value.tv_sec  = ntimeout.it_value.tv_usec = 0;
122
    setitimer(ITIMER_REAL,&ntimeout,NULL);	/* now stop timer */
123
    set_signal_handler(SIGALRM, SIG_IGN);
124
    }
125
#else
126
    /* 
127
     * So the workaround I used is to make it sleep by using
128
     * select() instead of setitimer()/pause().  select() is
129
     * perfectly happy being called with a timeout and
130
     * no file descriptors; it just sleeps until it hits the
131
     * timeout.  The only concern I had was that it might
132
     * implement its timeout with SIGALRM--there are some
133
     * Unices where this is done, because select() is a library
134
     * function--but apparently not.
135
     */
136
    {
137
    struct timeval timeout;
138
139
    timeout.tv_sec = seconds;
140
    timeout.tv_usec = 0;
141
    do {
142
	lastsig = 0;
143
	select(0,0,0,0, &timeout);
144
    } while (lastsig == SIGCHLD);
145
    }
146
#endif
147
#else /* EMX */
148
    alarm_latch = FALSE;
149
    set_signal_handler(SIGALRM, gotsigalrm);
150
    _beginthread(itimerthread, NULL, 32768, NULL);
151
    /* see similar code above */
152
    if (!alarm_latch)
153
	pause();
154
    set_signal_handler(SIGALRM, SIG_IGN);
155
#endif /* ! EMX */
156
    if (lastsig == SIGUSR1 || ((seconds && getuid() == ROOT_UID)
157
	&& lastsig == SIGHUP))
158
       awoken = TRUE;
159
160
    /* now lock out interrupts again */
161
    set_signal_handler(SIGUSR1, SIG_IGN);
162
    if (getuid() == ROOT_UID)
163
	set_signal_handler(SIGHUP, SIG_IGN);
164
165
    return(awoken ? lastsig : 0);
166
}
167
168
#ifdef MAIN
169
int main(int argc, char **argv)
170
{
171
    for (;;)
172
    {
173
	printf("How may I serve you, master?\n");
174
	interruptible_idle(5);
175
    }
176
}
177
#endif /* MAIN */
178
179
/* idle.c ends here */