1 /* $NetBSD: daemon-bozo.c,v 1.22 2020/10/15 04:21:53 mrg Exp $ */ 2 3 /* $eterna: daemon-bozo.c,v 1.24 2011/11/18 09:21:15 mrg Exp $ */ 4 5 /* 6 * Copyright (c) 1997-2019 Matthew R. Green 7 * All rights reserved. 8 * 9 * Redistribution and use in source and binary forms, with or without 10 * modification, are permitted provided that the following conditions 11 * are met: 12 * 1. Redistributions of source code must retain the above copyright 13 * notice, this list of conditions and the following disclaimer. 14 * 2. Redistributions in binary form must reproduce the above copyright 15 * notice, this list of conditions and the following disclaimer and 16 * dedication in the documentation and/or other materials provided 17 * with the distribution. 18 * 19 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR 20 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 21 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 22 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, 23 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, 24 * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 25 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED 26 * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 27 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 28 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 29 * SUCH DAMAGE. 30 * 31 */ 32 33 /* this code implements daemon mode for bozohttpd */ 34 35 #ifndef NO_DAEMON_MODE 36 37 #include <sys/param.h> 38 #include <sys/socket.h> 39 #include <sys/wait.h> 40 41 #include <netinet/in.h> 42 43 #include <assert.h> 44 #include <errno.h> 45 #include <netdb.h> 46 #include <poll.h> 47 #include <stdio.h> 48 #include <stdlib.h> 49 #include <string.h> 50 #include <unistd.h> 51 52 #include "bozohttpd.h" 53 54 static void sigchild(int); /* SIGCHLD handler */ 55 56 #ifndef POLLRDNORM 57 #define POLLRDNORM 0 58 #endif 59 #ifndef POLLRDBAND 60 #define POLLRDBAND 0 61 #endif 62 #ifndef INFTIM 63 #define INFTIM -1 64 #endif 65 #ifndef USE_ARG 66 #define USE_ARG(x) /*LINTED*/(void)&(x) 67 #endif 68 69 static const char* pidfile_path = NULL; 70 static pid_t pidfile_pid = 0; 71 72 static void 73 sigchild(int signo) 74 { 75 USE_ARG(signo); 76 while (waitpid(-1, NULL, WNOHANG) > 0) 77 /* nothing */; 78 } 79 80 /* Signal handler to exit in a controlled manner. This ensures that 81 * any atexit(3) handlers are properly executed. */ 82 BOZO_DEAD static void 83 controlled_exit(int signo) 84 { 85 USE_ARG(signo); 86 exit(EXIT_SUCCESS); 87 } 88 89 static void 90 remove_pidfile(void) 91 { 92 93 if (pidfile_path != NULL && pidfile_pid == getpid()) { 94 (void)unlink(pidfile_path); 95 pidfile_path = NULL; 96 } 97 } 98 99 static void 100 create_pidfile(bozohttpd_t *httpd) 101 { 102 FILE *file; 103 104 assert(pidfile_path == NULL); 105 106 if (httpd->pidfile == NULL) 107 return; 108 109 if (atexit(remove_pidfile) == -1) 110 bozoerr(httpd, 1, "Failed to install pidfile handler"); 111 112 if ((file = fopen(httpd->pidfile, "w")) == NULL) 113 bozoerr(httpd, 1, "Failed to create pidfile '%s'", 114 httpd->pidfile); 115 (void)fprintf(file, "%d\n", getpid()); 116 (void)fclose(file); 117 118 pidfile_path = httpd->pidfile; 119 pidfile_pid = getpid(); 120 121 debug((httpd, DEBUG_FAT, "Created pid file '%s' for pid %d", 122 pidfile_path, pidfile_pid)); 123 } 124 125 void 126 bozo_daemon_init(bozohttpd_t *httpd) 127 { 128 struct addrinfo h, *r, *r0; 129 const char *portnum; 130 int e, i, on = 1; 131 132 if (!httpd->background && !httpd->foreground) 133 return; 134 135 if (!httpd->background) 136 httpd->background = 1; 137 138 portnum = (httpd->bindport) ? httpd->bindport : "http"; 139 140 memset(&h, 0, sizeof(h)); 141 h.ai_family = PF_UNSPEC; 142 h.ai_socktype = SOCK_STREAM; 143 h.ai_flags = AI_PASSIVE; 144 e = getaddrinfo(httpd->bindaddress, portnum, &h, &r0); 145 if (e) 146 bozoerr(httpd, 1, "getaddrinfo([%s]:%s): %s", 147 httpd->bindaddress ? httpd->bindaddress : "*", 148 portnum, gai_strerror(e)); 149 for (r = r0; r != NULL; r = r->ai_next) 150 httpd->nsock++; 151 httpd->sock = bozomalloc(httpd, httpd->nsock * sizeof(*httpd->sock)); 152 httpd->fds = bozomalloc(httpd, httpd->nsock * sizeof(*httpd->fds)); 153 for (i = 0, r = r0; r != NULL; r = r->ai_next) { 154 httpd->sock[i] = socket(r->ai_family, SOCK_STREAM, 0); 155 if (httpd->sock[i] == -1) 156 continue; 157 if (setsockopt(httpd->sock[i], SOL_SOCKET, SO_REUSEADDR, &on, 158 sizeof(on)) == -1) 159 bozowarn(httpd, "setsockopt SO_REUSEADDR: %s", 160 strerror(errno)); 161 if (bind(httpd->sock[i], r->ai_addr, r->ai_addrlen) == -1) 162 continue; 163 if (listen(httpd->sock[i], SOMAXCONN) == -1) 164 continue; 165 httpd->fds[i].events = POLLIN | POLLPRI | POLLRDNORM | 166 POLLRDBAND | POLLERR; 167 httpd->fds[i].fd = httpd->sock[i]; 168 i++; 169 } 170 if (i == 0) 171 bozoerr(httpd, 1, "could not find any addresses to bind"); 172 httpd->nsock = i; 173 freeaddrinfo(r0); 174 175 if (httpd->foreground == 0) 176 daemon(1, 0); 177 178 create_pidfile(httpd); 179 180 bozowarn(httpd, "started in daemon mode as `%s' port `%s' root `%s'", 181 httpd->virthostname, portnum, httpd->slashdir); 182 183 signal(SIGHUP, controlled_exit); 184 signal(SIGINT, controlled_exit); 185 signal(SIGTERM, controlled_exit); 186 187 signal(SIGCHLD, sigchild); 188 } 189 190 void 191 bozo_daemon_closefds(bozohttpd_t *httpd) 192 { 193 int i; 194 195 for (i = 0; i < httpd->nsock; i++) 196 close(httpd->sock[i]); 197 } 198 199 static void 200 daemon_runchild(bozohttpd_t *httpd, int fd) 201 { 202 httpd->request_times++; 203 204 /* setup stdin/stdout/stderr */ 205 dup2(fd, 0); 206 dup2(fd, 1); 207 /*dup2(fd, 2);*/ 208 close(fd); 209 } 210 211 static int 212 daemon_poll_err(bozohttpd_t *httpd, int idx) 213 { 214 if ((httpd->fds[idx].revents & (POLLNVAL|POLLERR|POLLHUP)) == 0) 215 return 0; 216 217 bozowarn(httpd, "poll on fd %d pid %d revents %d: %s", 218 httpd->fds[idx].fd, getpid(), httpd->fds[idx].revents, 219 strerror(errno)); 220 bozowarn(httpd, "nsock = %d", httpd->nsock); 221 close(httpd->sock[idx]); 222 httpd->nsock--; 223 bozowarn(httpd, "nsock now = %d", httpd->nsock); 224 /* no sockets left */ 225 if (httpd->nsock == 0) 226 exit(0); 227 /* last socket closed is the easy case */ 228 if (httpd->nsock != idx) { 229 memmove(&httpd->fds[idx], &httpd->fds[idx+1], 230 (httpd->nsock - idx) * sizeof(*httpd->fds)); 231 memmove(&httpd->sock[idx], &httpd->sock[idx+1], 232 (httpd->nsock - idx) * sizeof(*httpd->sock)); 233 } 234 235 return 1; 236 } 237 238 /* 239 * the parent never returns from this function, only children that 240 * are ready to run... XXXMRG - still true in fork-lesser bozo? 241 */ 242 int 243 bozo_daemon_fork(bozohttpd_t *httpd) 244 { 245 int i; 246 247 debug((httpd, DEBUG_FAT, "%s: pid %u request_times %d", 248 __func__, getpid(), 249 httpd->request_times)); 250 /* if we've handled 5 files, exit and let someone else work */ 251 if (httpd->request_times > 5 || 252 (httpd->background == 2 && httpd->request_times > 0)) 253 _exit(0); 254 255 #if 1 256 if (httpd->request_times > 0) 257 _exit(0); 258 #endif 259 260 while (httpd->background) { 261 struct sockaddr_storage ss; 262 socklen_t slen; 263 int fd; 264 265 if (httpd->nsock == 0) 266 exit(0); 267 268 /* 269 * wait for a connection, then fork() and return NULL in 270 * the parent, who will come back here waiting for another 271 * connection. read the request in in the child, and return 272 * it, for processing. 273 */ 274 again: 275 if (poll(httpd->fds, (unsigned)httpd->nsock, INFTIM) == -1) { 276 /* fail on programmer errors */ 277 if (errno == EFAULT || 278 errno == EINVAL) 279 bozoerr(httpd, 1, "poll: %s", 280 strerror(errno)); 281 282 /* sleep on some temporary kernel failures */ 283 if (errno == ENOMEM || 284 errno == EAGAIN) 285 sleep(1); 286 287 goto again; 288 } 289 290 for (i = 0; i < httpd->nsock; i++) { 291 if (daemon_poll_err(httpd, i)) 292 break; 293 if (httpd->fds[i].revents == 0) 294 continue; 295 296 slen = sizeof(ss); 297 fd = accept(httpd->fds[i].fd, 298 (struct sockaddr *)(void *)&ss, &slen); 299 if (fd == -1) { 300 if (errno == EFAULT || 301 errno == EINVAL) 302 bozoerr(httpd, 1, "accept: %s", 303 strerror(errno)); 304 305 if (errno == ENOMEM || 306 errno == EAGAIN) 307 sleep(1); 308 309 continue; 310 } 311 312 #if 0 313 /* 314 * This code doesn't work. It interacts very poorly 315 * with ~user translation and needs to be fixed. 316 */ 317 if (httpd->request_times > 0) { 318 daemon_runchild(httpd, fd); 319 return 0; 320 } 321 #endif 322 323 switch (fork()) { 324 case -1: /* eep, failure */ 325 bozowarn(httpd, "fork() failed, sleeping for " 326 "10 seconds: %s", strerror(errno)); 327 close(fd); 328 sleep(10); 329 break; 330 331 case 0: /* child */ 332 daemon_runchild(httpd, fd); 333 return 0; 334 335 default: /* parent */ 336 close(fd); 337 break; 338 } 339 } 340 } 341 return 0; 342 } 343 344 #endif /* NO_DAEMON_MODE */ 345