1 /* $NetBSD: ipropd_common.c,v 1.2 2017/01/28 21:31:49 christos Exp $ */
2
3 /*
4 * Copyright (c) 1997 - 2007 Kungliga Tekniska Högskolan
5 * (Royal Institute of Technology, Stockholm, Sweden).
6 * All rights reserved.
7 *
8 * Redistribution and use in source and binary forms, with or without
9 * modification, are permitted provided that the following conditions
10 * are met:
11 *
12 * 1. Redistributions of source code must retain the above copyright
13 * notice, this list of conditions and the following disclaimer.
14 *
15 * 2. Redistributions in binary form must reproduce the above copyright
16 * notice, this list of conditions and the following disclaimer in the
17 * documentation and/or other materials provided with the distribution.
18 *
19 * 3. Neither the name of the Institute nor the names of its contributors
20 * may be used to endorse or promote products derived from this software
21 * without specific prior written permission.
22 *
23 * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND
24 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
25 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
26 * ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE
27 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
28 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
29 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
30 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
31 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
32 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
33 * SUCH DAMAGE.
34 */
35
36 #include "iprop.h"
37
38 #if defined(HAVE_FORK) && defined(HAVE_WAITPID)
39 #include <sys/types.h>
40 #include <sys/wait.h>
41 #endif
42
43 sig_atomic_t exit_flag;
44
45 static RETSIGTYPE
sigterm(int sig)46 sigterm(int sig)
47 {
48 exit_flag = sig;
49 }
50
51 void
setup_signal(void)52 setup_signal(void)
53 {
54 #ifdef HAVE_SIGACTION
55 {
56 struct sigaction sa;
57
58 sa.sa_flags = 0;
59 sa.sa_handler = sigterm;
60 sigemptyset(&sa.sa_mask);
61
62 sigaction(SIGINT, &sa, NULL);
63 sigaction(SIGTERM, &sa, NULL);
64 sigaction(SIGXCPU, &sa, NULL);
65
66 sa.sa_handler = SIG_IGN;
67 sigaction(SIGPIPE, &sa, NULL);
68 }
69 #else
70 signal(SIGINT, sigterm);
71 signal(SIGTERM, sigterm);
72 #ifndef NO_SIGXCPU
73 signal(SIGXCPU, sigterm);
74 #endif
75 #ifndef NO_SIGPIPE
76 signal(SIGPIPE, SIG_IGN);
77 #endif
78 #endif
79 }
80
81 /*
82 * Fork a child to run the service, and restart it if it dies.
83 *
84 * Returns -1 if not supported, else a file descriptor that the service
85 * should select() for. Any events on that file descriptor should cause
86 * the caller to exit immediately, as that means that the restarter
87 * exited.
88 *
89 * The service's normal exit status values should be should be taken
90 * from enum ipropd_exit_code. IPROPD_FATAL causes the restarter to
91 * stop restarting the service and to exit.
92 *
93 * A count of restarts is output via the `countp' argument, if it is
94 * non-NULL. This is useful for testing this function (e.g., kill the
95 * restarter after N restarts and check that the child gets the signal
96 * sent to it).
97 *
98 * This requires fork() and waitpid() (otherwise returns -1). Ignoring
99 * SIGCHLD, of course, would be bad.
100 *
101 * We could support this on Windows by spawning a child with mostly the
102 * same arguments as the restarter process.
103 */
104 int
restarter(krb5_context context,size_t * countp)105 restarter(krb5_context context, size_t *countp)
106 {
107 #if defined(HAVE_FORK) && defined(HAVE_WAITPID)
108 struct timeval tmout;
109 pid_t pid = -1;
110 pid_t wpid = -1;
111 int status;
112 int fds[2];
113 int fds2[2];
114 size_t count = 0;
115 fd_set readset;
116
117 fds[0] = -1;
118 fds[1] = -1;
119 fds2[0] = -1;
120 fds2[1] = -1;
121
122 signal(SIGCHLD, SIG_DFL);
123
124 while (!exit_flag) {
125 /* Close the pipe ends we keep open */
126 if (fds[1] != -1)
127 (void) close(fds[1]);
128 if (fds2[0] != -1)
129 (void) close(fds2[1]);
130
131 /* A pipe so the child can detect the parent's death */
132 if (pipe(fds) == -1) {
133 krb5_err(context, 1, errno,
134 "Could not setup pipes in service restarter");
135 }
136
137 /* A pipe so the parent can detect the child's death */
138 if (pipe(fds2) == -1) {
139 krb5_err(context, 1, errno,
140 "Could not setup pipes in service restarter");
141 }
142
143 fflush(stdout);
144 fflush(stderr);
145
146 pid = fork();
147 if (pid == -1)
148 krb5_err(context, 1, errno, "Could not fork in service restarter");
149 if (pid == 0) {
150 if (countp != NULL)
151 *countp = count;
152 (void) close(fds[1]);
153 (void) close(fds2[0]);
154 return fds[0];
155 }
156
157 count++;
158
159 (void) close(fds[0]);
160 (void) close(fds2[1]);
161
162 do {
163 wpid = waitpid(pid, &status, 0);
164 } while (wpid == -1 && errno == EINTR && !exit_flag);
165 if (wpid == -1 && errno == EINTR)
166 break; /* We were signaled; gotta kill the child and exit */
167 if (wpid == -1) {
168 if (errno != ECHILD) {
169 warn("waitpid() failed; killing restarter's child process");
170 kill(pid, SIGTERM);
171 }
172 krb5_err(context, 1, errno, "restarter failed waiting for child");
173 }
174
175 assert(wpid == pid);
176 wpid = -1;
177 pid = -1;
178 if (WIFEXITED(status)) {
179 switch (WEXITSTATUS(status)) {
180 case IPROPD_DONE:
181 exit(0);
182 case IPROPD_RESTART_SLOW:
183 if (exit_flag)
184 exit(1);
185 krb5_warnx(context, "Waiting 2 minutes to restart");
186 sleep(120);
187 continue;
188 case IPROPD_FATAL:
189 krb5_errx(context, WEXITSTATUS(status),
190 "Sockets and pipes not supported for "
191 "iprop log files");
192 case IPROPD_RESTART:
193 default:
194 if (exit_flag)
195 exit(1);
196 /* Add exponential backoff (with max backoff)? */
197 krb5_warnx(context, "Waiting 30 seconds to restart");
198 sleep(30);
199 continue;
200 }
201 }
202 /* else */
203 krb5_warnx(context, "Child was killed; waiting 30 seconds to restart");
204 sleep(30);
205 }
206
207 if (pid == -1)
208 exit(0); /* No dead child to reap; done */
209
210 assert(pid > 0);
211 if (wpid != pid) {
212 warnx("Interrupted; killing child (pid %ld) with %d",
213 (long)pid, exit_flag);
214 krb5_warnx(context, "Interrupted; killing child (pid %ld) with %d",
215 (long)pid, exit_flag);
216 kill(pid, exit_flag);
217
218 /* Wait up to one second for the child */
219 tmout.tv_sec = 1;
220 tmout.tv_usec = 0;
221 FD_ZERO(&readset);
222 FD_SET(fds2[0], &readset);
223 /* We don't care why select() returns */
224 (void) select(fds2[0] + 1, &readset, NULL, NULL, &tmout);
225 /*
226 * We haven't reaped the child yet; if it's a zombie, then
227 * SIGKILLing it won't hurt. If it's not a zombie yet, well,
228 * we're out of patience.
229 */
230 kill(pid, SIGKILL);
231 do {
232 wpid = waitpid(pid, &status, 0);
233 } while (wpid != pid && errno == EINTR);
234 if (wpid == -1)
235 krb5_err(context, 1, errno, "restarter failed waiting for child");
236 }
237
238 /* Finally, the child is dead and reaped */
239 if (WIFEXITED(status))
240 exit(WEXITSTATUS(status));
241 if (WIFSIGNALED(status)) {
242 switch (WTERMSIG(status)) {
243 case SIGTERM:
244 case SIGXCPU:
245 case SIGINT:
246 exit(0);
247 default:
248 /*
249 * Attempt to set the same exit status for the parent as for
250 * the child.
251 */
252 kill(getpid(), WTERMSIG(status));
253 /*
254 * We can get past the self-kill if we inherited a SIG_IGN
255 * disposition that the child reset to SIG_DFL.
256 */
257 }
258 }
259 exit(1);
260 #else
261 if (countp != NULL)
262 *countp = 0;
263 errno = ENOTSUP;
264 return -1;
265 #endif
266 }
267
268