1/***********************************************************************
2*
3* event_sig.c
4*
5* Code for handling signals nicely (synchronously) and for dealing
6* with reaping child processes.
7*
8* Copyright (C) 2002 by Roaring Penguin Software Inc.
9*
10* This software may be distributed under the terms of the GNU General
11* Public License, Version 2, or (at your option) any later version.
12*
13* LIC: GPL
14*
15***********************************************************************/
16
17static char const RCSID[] =
18"$Id$";
19
20#define _POSIX_SOURCE 1 /* For sigaction defines */
21#define _BSD_SOURCE   1 /* For SA_RESTART */
22
23#include <signal.h>
24#include <sys/types.h>
25#include <sys/wait.h>
26#include <errno.h>
27#include <stddef.h>
28#include <fcntl.h>
29
30#include "event.h"
31#include "hash.h"
32
33/* Kludge for figuring out NSIG */
34#ifdef NSIG
35#define MAX_SIGNALS NSIG
36#elif defined(_NSIG)
37#define MAX_SIGNALS _NSIG
38#else
39#define MAX_SIGNALS 256  /* Should be safe... */
40#endif
41
42/* A structure for a "synchronous" signal handler */
43struct SynchronousSignalHandler {
44    int fired;			/* Have we received this signal? */
45    void (*handler)(int sig);	/* Handler function              */
46};
47
48/* A structure for calling back when a child dies */
49struct ChildEntry {
50    hash_bucket hash;
51    void (*handler)(pid_t pid, int status, void *data);
52    pid_t pid;
53    void *data;
54};
55
56static struct SynchronousSignalHandler SignalHandlers[MAX_SIGNALS];
57static int Pipe[2] = {-1, -1};
58static pid_t MyPid = (pid_t) -1;
59
60static EventHandler *PipeHandler = NULL;
61static hash_table child_process_table;
62
63static unsigned int child_hash(void *data)
64{
65    return (unsigned int) ((struct ChildEntry *) data)->pid;
66}
67
68static int child_compare(void *d1, void *d2)
69{
70    return ((struct ChildEntry *)d1)->pid != ((struct ChildEntry *)d2)->pid;
71}
72
73/**********************************************************************
74* %FUNCTION: DoPipe
75* %ARGUMENTS:
76*  es -- event selector
77*  fd -- readable file descriptor
78*  flags -- flags from event system
79*  data -- ignored
80* %RETURNS:
81*  Nothing
82* %DESCRIPTION:
83*  Called when an async signal handler wants attention.  This function
84*  fires all "synchronous" signal handlers.
85***********************************************************************/
86static void
87DoPipe(EventSelector *es,
88       int fd,
89       unsigned int flags,
90       void *data)
91{
92    char buf[64];
93    int i;
94    sigset_t set;
95
96    /* Clear pipe */
97    while (read(fd, buf, 64) == 64) {
98	;
99    }
100
101    /* Fire handlers */
102    for (i=0; i<MAX_SIGNALS; i++) {
103	if (SignalHandlers[i].fired &&
104	    SignalHandlers[i].handler) {
105
106	    /* Block signal while we call its handler */
107	    sigemptyset(&set);
108	    sigaddset(&set, i);
109	    sigprocmask(SIG_BLOCK, &set, NULL);
110
111	    SignalHandlers[i].handler(i);
112
113	    SignalHandlers[i].fired = 0;
114
115	    /* Unblock signal */
116	    sigprocmask(SIG_UNBLOCK, &set, NULL);
117	}
118    }
119}
120
121/**********************************************************************
122* %FUNCTION: sig_handler
123* %ARGUMENTS:
124*  sig -- signal number
125* %RETURNS:
126*  Nothing
127* %DESCRIPTION:
128*  Marks a signal as having "fired"; fills IPC pipe.
129***********************************************************************/
130static void
131sig_handler(int sig)
132{
133    if (sig <0 || sig > MAX_SIGNALS) {
134	/* Ooops... */
135	return;
136    }
137    if (getpid() != MyPid) {
138	/* Spuriously-caught signal caught in child! */
139	return;
140    }
141
142    /* If there's no handler, ignore it */
143    if (!SignalHandlers[sig].handler) {
144	return;
145    }
146
147    SignalHandlers[sig].fired = 1;
148    int errno_save = errno;
149    write(Pipe[1], &sig, 1);
150    errno = errno_save;
151}
152
153/**********************************************************************
154* %FUNCTION: child_handler
155* %ARGUMENTS:
156*  sig -- signal number (whoop-dee-doo)
157* %RETURNS:
158*  Nothing
159* %DESCRIPTION:
160*  Called *SYNCHRONOUSLY* to reap dead children.  SIGCHLD is blocked
161*  during the execution of this function.
162***********************************************************************/
163static void
164child_handler(int sig)
165{
166    int status;
167    int pid;
168    struct ChildEntry *ce;
169    struct ChildEntry candidate;
170
171    while ((pid = waitpid(-1, &status, WNOHANG)) > 0) {
172	candidate.pid = (pid_t) pid;
173
174	ce = hash_find(&child_process_table, &candidate);
175	if (ce) {
176	    hash_remove(&child_process_table, ce);
177	    if (ce->handler) {
178		ce->handler(pid, status, ce->data);
179	    }
180	    free(ce);
181	}
182    }
183}
184
185/**********************************************************************
186* %FUNCTION: SetupPipes (static)
187* %ARGUMENTS:
188*  es -- event selector
189* %RETURNS:
190*  0 on success; -1 on failure
191* %DESCRIPTION:
192*  Sets up pipes with an event handler to handle IPC from a signal handler
193***********************************************************************/
194static int
195SetupPipes(EventSelector *es)
196{
197    int flags;
198    int i;
199
200    /* If already done, do nothing */
201    if (PipeHandler) return 0;
202
203    MyPid = getpid();
204
205    /* Initialize the child-process hash table */
206    hash_init(&child_process_table,
207	      offsetof(struct ChildEntry, hash),
208	      child_hash,
209	      child_compare);
210
211    /* Open pipe to self */
212    if (pipe(Pipe) < 0) {
213	return -1;
214    }
215
216    /* Make pipes non-blocking */
217    for (i=0; i<=1; i++) {
218	flags = fcntl(Pipe[i], F_GETFL, 0);
219	if (flags != -1) {
220	    flags = fcntl(Pipe[i], F_SETFL, flags | O_NONBLOCK);
221	}
222	if (flags == -1) {
223	    close(Pipe[0]);
224	    close(Pipe[1]);
225	    return -1;
226	}
227    }
228
229    PipeHandler = Event_AddHandler(es, Pipe[0],
230				   EVENT_FLAG_READABLE, DoPipe, NULL);
231    if (!PipeHandler) {
232	int old_errno = errno;
233	close(Pipe[0]);
234	close(Pipe[1]);
235	errno = old_errno;
236	return -1;
237    }
238    return 0;
239}
240
241/**********************************************************************
242* %FUNCTION: Event_HandleSignal
243* %ARGUMENTS:
244*  es -- event selector
245*  sig -- signal number
246*  handler -- handler to call when signal is raised.  Handler is called
247*             "synchronously" as events are processed by event loop.
248* %RETURNS:
249*  0 on success, -1 on error.
250* %DESCRIPTION:
251*  Sets up a "synchronous" signal handler.
252***********************************************************************/
253int
254Event_HandleSignal(EventSelector *es,
255		   int sig,
256		   void (*handler)(int sig))
257{
258    struct sigaction act;
259
260    if (SetupPipes(es) < 0) return -1;
261
262    act.sa_handler = sig_handler;
263    sigemptyset(&act.sa_mask);
264    act.sa_flags = 0;
265#ifdef SA_RESTART
266    act.sa_flags |= SA_RESTART;
267#endif
268    if (sig == SIGCHLD) {
269	act.sa_flags |= SA_NOCLDSTOP;
270    }
271    if (sigaction(sig, &act, NULL) < 0) return -1;
272
273    SignalHandlers[sig].handler = handler;
274
275    return 0;
276}
277
278/**********************************************************************
279* %FUNCTION: Event_HandleChildExit
280* %ARGUMENTS:
281*  es -- event selector
282*  pid -- process-ID of child to wait for
283*  handler -- function to call when child exits
284*  data -- data to pass to handler when child exits
285* %RETURNS:
286*  0 on success, -1 on failure.
287* %DESCRIPTION:
288*  Sets things up so that when a child exits, handler() will be called
289*  with the pid of the child and "data" as arguments.  The call will
290*  be synchronous (part of the normal event loop on es).
291***********************************************************************/
292int
293Event_HandleChildExit(EventSelector *es,
294		      pid_t pid,
295		      void (*handler)(pid_t, int, void *),
296		      void *data)
297{
298    struct ChildEntry *ce;
299    sigset_t set;
300
301    if (Event_HandleSignal(es, SIGCHLD, child_handler) < 0) return -1;
302    ce = malloc(sizeof(struct ChildEntry));
303    if (!ce) return -1;
304    ce->pid = pid;
305    ce->data = data;
306    ce->handler = handler;
307
308    /* Critical section: Don't let SIGCHLD mess hash_insert */
309    sigemptyset(&set);
310    sigaddset(&set, SIGCHLD);
311    sigprocmask(SIG_BLOCK, &set, NULL);
312
313    hash_insert(&child_process_table, ce);
314
315    sigprocmask(SIG_UNBLOCK, &set, NULL);
316
317    return 0;
318}
319