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