]> Pileus Git - ~andy/fetchmail/blob - idle.c
Attempt merging from 6.3.24.
[~andy/fetchmail] / idle.c
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 void 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 int interruptible_idle(int seconds)
54 /* time for a pause in the action; return TRUE if awakened by signal */
55 {
56     int awoken = FALSE;
57
58 #ifdef SLEEP_WITH_ALARM         /* not normally on */
59     /*
60      * We can't use sleep(3) here because we need an alarm(3)
61      * equivalent in order to implement server nonresponse timeout.
62      * We'll just assume setitimer(2) is available since fetchmail
63      * has to have a BSDoid socket layer to work at all.
64      */
65     /* 
66      * This code stopped working under glibc-2, apparently due
67      * to the change in signal(2) semantics.  (The siginterrupt
68      * line, added later, should fix this problem.) John Stracke
69      * <francis@netscape.com> wrote:
70      *
71      * The problem seems to be that, after hitting the interval
72      * timer while talking to the server, the process no longer
73      * responds to SIGALRM.  I put in printf()s to see when it
74      * reached the pause() for the poll interval, and I checked
75      * the return from setitimer(), and everything seemed to be
76      * working fine, except that the pause() just ignored SIGALRM.
77      * I thought maybe the itimer wasn't being fired, so I hit
78      * it with a SIGALRM from the command line, and it ignored
79      * that, too.  SIGUSR1 woke it up just fine, and it proceeded
80      * to repoll--but, when the dummy server didn't respond, it
81      * never timed out, and SIGALRM wouldn't make it.
82      *
83      * (continued below...)
84      */
85     {
86     struct itimerval ntimeout;
87
88     ntimeout.it_interval.tv_sec = 5; /* repeat alarm every 5 secs */
89     ntimeout.it_interval.tv_usec = 0;
90     ntimeout.it_value.tv_sec  = seconds;
91     ntimeout.it_value.tv_usec = 0;
92
93     alarm_latch = FALSE;
94     set_signal_handler(SIGALRM, gotsigalrm);    /* first trap signals */
95     setitimer(ITIMER_REAL,&ntimeout,NULL);      /* then start timer */
96     /* there is a very small window between the next two lines */
97     /* which could result in a deadlock.  But this will now be  */
98     /* caught by periodic alarms (see it_interval) */
99     if (!alarm_latch)
100         pause();
101     /* stop timer */
102     ntimeout.it_interval.tv_sec = ntimeout.it_interval.tv_usec = 0;
103     ntimeout.it_value.tv_sec  = ntimeout.it_value.tv_usec = 0;
104     setitimer(ITIMER_REAL,&ntimeout,NULL);      /* now stop timer */
105     set_signal_handler(SIGALRM, SIG_IGN);
106     }
107 #else
108     /* 
109      * So the workaround I used is to make it sleep by using
110      * select() instead of setitimer()/pause().  select() is
111      * perfectly happy being called with a timeout and
112      * no file descriptors; it just sleeps until it hits the
113      * timeout.  The only concern I had was that it might
114      * implement its timeout with SIGALRM--there are some
115      * Unices where this is done, because select() is a library
116      * function--but apparently not.
117      */
118     {
119     struct timeval timeout;
120
121     timeout.tv_sec = seconds;
122     timeout.tv_usec = 0;
123     do {
124         lastsig = 0;
125         select(0,0,0,0, &timeout);
126     } while (lastsig == SIGCHLD);
127     }
128 #endif
129     if (lastsig == SIGUSR1 || ((seconds && getuid() == ROOT_UID)
130         && lastsig == SIGHUP))
131        awoken = TRUE;
132
133     /* now lock out interrupts again */
134     set_signal_handler(SIGUSR1, SIG_IGN);
135     if (getuid() == ROOT_UID)
136         set_signal_handler(SIGHUP, SIG_IGN);
137
138     return(awoken ? lastsig : 0);
139 }
140
141 #ifdef MAIN
142 int main(int argc, char **argv)
143 {
144     for (;;)
145     {
146         printf("How may I serve you, master?\n");
147         interruptible_idle(5);
148     }
149 }
150 #endif /* MAIN */
151
152 /* idle.c ends here */