Add infrastructure to handle child processes exiting.
[ircu2.10.12-pk.git] / ircd / ircd_signal.c
1 /*
2  * IRC - Internet Relay Chat, ircd/ircd_signal.c
3  * Copyright (C) 1990 Jarkko Oikarinen and
4  *                    University of Oulu, Computing Center
5  *
6  * This program is free software; you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License as published by
8  * the Free Software Foundation; either version 1, or (at your option)
9  * any later version.
10  *
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with this program; if not, write to the Free Software
18  * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
19  */
20 /** @file
21  * @brief Signal handlers for ircu.
22  * @version $Id$
23  */
24 #include "config.h"
25
26 #include "ircd.h"
27 #include "ircd_alloc.h"
28 #include "ircd_events.h"
29 #include "ircd_log.h"
30 #include "ircd_signal.h"
31 #include "s_conf.h"
32
33 /* #include <assert.h> -- Now using assert in ircd_log.h */
34 #include <signal.h>
35 #include <string.h>
36 #include <sys/wait.h>
37 #include <unistd.h>
38
39 /** Records a function to be called when a child process terminates. */
40 struct ChildRecord {
41   struct ChildRecord *next;
42   SigChldCallBack call;
43   void *datum;
44   pid_t cpid;
45 };
46
47 /** Counts various types of signals that we receive. */
48 static struct tag_SignalCounter {
49   unsigned int alrm; /**< Received SIGALRM count. */
50   unsigned int hup;  /**< Received SIGHUP count. */
51   unsigned int chld; /**< Received SIGCHLD count. */
52 } SignalCounter;
53
54 /** Event generator for SIGHUP. */
55 static struct Signal sig_hup;
56 /** Event generator for SIGINT. */
57 static struct Signal sig_int;
58 /** Event generator for SIGTERM. */
59 static struct Signal sig_term;
60 /** Event generator for SIGCHLD. */
61 static struct Signal sig_chld;
62 /** List of active child process callback requests. */
63 static struct ChildRecord *children;
64 /** List of inactive (free) child records. */
65 static struct ChildRecord *crec_freelist;
66
67 /* Make sure we have a definition for SIGCHLD. */
68 #if !defined(SIGCHLD)
69 # define SIGCHLD SIGCLD
70 #endif
71
72 /** Signal handler for SIGALRM.
73  * @param[in] sig Signal number (ignored).
74  */
75 static void sigalrm_handler(int sig)
76 {
77   ++SignalCounter.alrm;
78 }
79
80 /** Signal callback for SIGTERM.
81  * @param[in] ev Signal event descriptor.
82  */
83 static void sigterm_callback(struct Event* ev)
84 {
85   assert(0 != ev_signal(ev));
86   assert(ET_SIGNAL == ev_type(ev));
87   assert(SIGTERM == sig_signal(ev_signal(ev)));
88   assert(SIGTERM == ev_data(ev));
89
90   server_die("received signal SIGTERM");
91 }
92
93 /** Signal callback for SIGHUP.
94  * @param[in] ev Signal event descriptor.
95  */
96 static void sighup_callback(struct Event* ev)
97 {
98   assert(0 != ev_signal(ev));
99   assert(ET_SIGNAL == ev_type(ev));
100   assert(SIGHUP == sig_signal(ev_signal(ev)));
101   assert(SIGHUP == ev_data(ev));
102
103   ++SignalCounter.hup;
104   rehash(&me, 1);
105 }
106
107 /** Signal callback for SIGINT.
108  * @param[in] ev Signal event descriptor.
109  */
110 static void sigint_callback(struct Event* ev)
111 {
112   assert(0 != ev_signal(ev));
113   assert(ET_SIGNAL == ev_type(ev));
114   assert(SIGINT == sig_signal(ev_signal(ev)));
115   assert(SIGINT == ev_data(ev));
116
117   server_restart("caught signal: SIGINT");
118 }
119
120 /** Allocate a child callback record.
121  * @return Newly allocated callback record.
122  */
123 static struct ChildRecord *alloc_crec(void)
124 {
125   struct ChildRecord *crec;
126
127   if (crec_freelist)
128   {
129     crec = crec_freelist;
130     crec_freelist = crec->next;
131   }
132   else
133   {
134     crec = MyCalloc(1, sizeof(*crec));
135   }
136
137   crec->next = NULL;
138   return crec;
139 }
140
141 /** Release \a crec, which is after \a prev.
142  * @param[in] crec Child process callback record to release.
143  */
144 static void release_crec(struct ChildRecord *crec)
145 {
146   memset(crec, 0, sizeof(*crec));
147   crec->next = crec_freelist;
148   crec_freelist = crec;
149 }
150
151 /** Register a function to be called when a child process terminates.
152  * @param[in] child Child process ID.
153  * @param[in] call Function to call when process \a child terminates.
154  * @param[in] datum Additional data parameter to pass to \a call.
155  */
156 void register_child(pid_t child, SigChldCallBack call, void *datum)
157 {
158   struct ChildRecord *crec;
159
160   crec = alloc_crec();
161   /* Link into #children list. */
162   crec->next = children;
163   children = crec;
164   /* Fill in user fields. */
165   crec->call = call;
166   crec->datum = datum;
167   crec->cpid = child;
168 }
169
170 /** Unregister all callbacks for a child process, optionally calling
171  * them first.
172  * @param[in] child Child process ID to unregister.
173  * @param[in] do_call If non-zero, make the callbacks.
174  * @param[in] status If \a do_call is non-zero, the child's exit status.
175  */
176 static void do_unregister_child(pid_t child, int do_call, int status)
177 {
178   struct ChildRecord *crec = children;
179   struct ChildRecord *prev = NULL;
180
181   while (crec != NULL)
182   {
183     if (crec->cpid == child)
184     {
185       if (do_call)
186         crec->call(child, crec->datum, status);
187
188       if (prev)
189         prev->next = crec->next;
190       else
191         children = crec->next;
192
193       release_crec(crec);
194     }
195     else
196       prev = crec;
197     crec = prev ? prev->next : children;
198   }
199 }
200
201 /** Unregister all callbacks for a child process.
202  * @param[in] child Child process ID to unregister.
203  */
204 void unregister_child(pid_t child)
205 {
206   do_unregister_child(child, 0, 0);
207 }
208
209 /** Signal handler for SIGCHLD.
210  * @param[in] ev Signal event descriptor.
211  */
212 static void sigchld_callback(struct Event *ev)
213 {
214   pid_t cpid;
215   int status;
216
217   ++SignalCounter.chld;
218   do {
219     cpid = waitpid(-1, &status, WNOHANG);
220     if (cpid > 0)
221       do_unregister_child(cpid, 1, status);
222   } while (cpid > 0);
223 }
224
225 /** Register all necessary signal handlers. */
226 void setup_signals(void)
227 {
228   struct sigaction act;
229
230   act.sa_handler = SIG_IGN;
231   act.sa_flags = 0;
232   sigemptyset(&act.sa_mask);
233   sigaddset(&act.sa_mask, SIGPIPE);
234   sigaddset(&act.sa_mask, SIGALRM);
235 #ifdef  SIGWINCH
236   sigaddset(&act.sa_mask, SIGWINCH);
237   sigaction(SIGWINCH, &act, 0);
238 #endif
239   sigaction(SIGPIPE, &act, 0);
240
241   act.sa_handler = sigalrm_handler;
242   sigaction(SIGALRM, &act, 0);
243
244   signal_add(&sig_hup, sighup_callback, 0, SIGHUP);
245   signal_add(&sig_int, sigint_callback, 0, SIGINT);
246   signal_add(&sig_term, sigterm_callback, 0, SIGTERM);
247   signal_add(&sig_chld, sigchld_callback, 0, SIGCHLD);
248
249 #ifdef HAVE_RESTARTABLE_SYSCALLS
250   /*
251    * At least on Apollo sr10.1 it seems continuing system calls
252    * after signal is the default. The following 'siginterrupt'
253    * should change that default to interrupting calls.
254    */
255   siginterrupt(SIGALRM, 1);
256 #endif
257 }
258
259 /** Kill and clean up all child processes. */
260 void reap_children(void)
261 {
262   /* Send SIGTERM to all children in process group.  Sleep for a
263    * second to let them exit before we try to clean them up.
264    */
265   kill(0, SIGTERM);
266   sleep(1);
267   sigchld_callback(NULL);
268 }