xref: /netbsd-src/crypto/external/bsd/heimdal/dist/lib/kadm5/ipropd_common.c (revision d3273b5b76f5afaafe308cead5511dbb8df8c5e9)
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