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
43sig_atomic_t exit_flag;
44
45static RETSIGTYPE
46sigterm(int sig)
47{
48    exit_flag = sig;
49}
50
51void
52setup_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 */
104int
105restarter(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