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