daemon-bozo.c revision 1.19 1 /* $NetBSD: daemon-bozo.c,v 1.19 2018/11/22 08:54:08 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-2018 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
66 static const char* pidfile_path = NULL;
67 static pid_t pidfile_pid = 0;
68
69 /* ARGSUSED */
70 static void
71 sigchild(int signo)
72 {
73
74 while (waitpid(-1, NULL, WNOHANG) > 0)
75 /* nothing */;
76 }
77
78 /* Signal handler to exit in a controlled manner. This ensures that
79 * any atexit(3) handlers are properly executed. */
80 /* ARGSUSED */
81 BOZO_DEAD static void
82 controlled_exit(int signo)
83 {
84
85 exit(EXIT_SUCCESS);
86 }
87
88 static void
89 remove_pidfile(void)
90 {
91
92 if (pidfile_path != NULL && pidfile_pid == getpid()) {
93 (void)unlink(pidfile_path);
94 pidfile_path = NULL;
95 }
96 }
97
98 static void
99 create_pidfile(bozohttpd_t *httpd)
100 {
101 FILE *file;
102
103 assert(pidfile_path == NULL);
104
105 if (httpd->pidfile == NULL)
106 return;
107
108 if (atexit(remove_pidfile) == -1)
109 bozoerr(httpd, 1, "Failed to install pidfile handler");
110
111 if ((file = fopen(httpd->pidfile, "w")) == NULL)
112 bozoerr(httpd, 1, "Failed to create pidfile '%s'",
113 httpd->pidfile);
114 (void)fprintf(file, "%d\n", getpid());
115 (void)fclose(file);
116
117 pidfile_path = httpd->pidfile;
118 pidfile_pid = getpid();
119
120 debug((httpd, DEBUG_FAT, "Created pid file '%s' for pid %d",
121 pidfile_path, pidfile_pid));
122 }
123
124 void
125 bozo_daemon_init(bozohttpd_t *httpd)
126 {
127 struct addrinfo h, *r, *r0;
128 const char *portnum;
129 int e, i, on = 1;
130
131 if (!httpd->background)
132 return;
133
134 portnum = (httpd->bindport) ? httpd->bindport : "http";
135
136 memset(&h, 0, sizeof(h));
137 h.ai_family = PF_UNSPEC;
138 h.ai_socktype = SOCK_STREAM;
139 h.ai_flags = AI_PASSIVE;
140 e = getaddrinfo(httpd->bindaddress, portnum, &h, &r0);
141 if (e)
142 bozoerr(httpd, 1, "getaddrinfo([%s]:%s): %s",
143 httpd->bindaddress ? httpd->bindaddress : "*",
144 portnum, gai_strerror(e));
145 for (r = r0; r != NULL; r = r->ai_next)
146 httpd->nsock++;
147 httpd->sock = bozomalloc(httpd, httpd->nsock * sizeof(*httpd->sock));
148 httpd->fds = bozomalloc(httpd, httpd->nsock * sizeof(*httpd->fds));
149 for (i = 0, r = r0; r != NULL; r = r->ai_next) {
150 httpd->sock[i] = socket(r->ai_family, SOCK_STREAM, 0);
151 if (httpd->sock[i] == -1)
152 continue;
153 if (setsockopt(httpd->sock[i], SOL_SOCKET, SO_REUSEADDR, &on,
154 sizeof(on)) == -1)
155 bozowarn(httpd, "setsockopt SO_REUSEADDR: %s",
156 strerror(errno));
157 if (bind(httpd->sock[i], r->ai_addr, r->ai_addrlen) == -1)
158 continue;
159 if (listen(httpd->sock[i], SOMAXCONN) == -1)
160 continue;
161 httpd->fds[i].events = POLLIN | POLLPRI | POLLRDNORM |
162 POLLRDBAND | POLLERR;
163 httpd->fds[i].fd = httpd->sock[i];
164 i++;
165 }
166 if (i == 0)
167 bozoerr(httpd, 1, "could not find any addresses to bind");
168 httpd->nsock = i;
169 freeaddrinfo(r0);
170
171 if (httpd->foreground == 0)
172 daemon(1, 0);
173
174 create_pidfile(httpd);
175
176 bozowarn(httpd, "started in daemon mode as `%s' port `%s' root `%s'",
177 httpd->virthostname, portnum, httpd->slashdir);
178
179 signal(SIGHUP, controlled_exit);
180 signal(SIGINT, controlled_exit);
181 signal(SIGTERM, controlled_exit);
182
183 signal(SIGCHLD, sigchild);
184 }
185
186 void
187 bozo_daemon_closefds(bozohttpd_t *httpd)
188 {
189 int i;
190
191 for (i = 0; i < httpd->nsock; i++)
192 close(httpd->sock[i]);
193 }
194
195 static void
196 daemon_runchild(bozohttpd_t *httpd, int fd)
197 {
198 httpd->request_times++;
199
200 /* setup stdin/stdout/stderr */
201 dup2(fd, 0);
202 dup2(fd, 1);
203 /*dup2(fd, 2);*/
204 close(fd);
205 }
206
207 static int
208 daemon_poll_err(bozohttpd_t *httpd, int fd, int idx)
209 {
210 if ((httpd->fds[idx].revents & (POLLNVAL|POLLERR|POLLHUP)) == 0)
211 return 0;
212
213 bozowarn(httpd, "poll on fd %d pid %d revents %d: %s",
214 httpd->fds[idx].fd, getpid(), httpd->fds[idx].revents,
215 strerror(errno));
216 bozowarn(httpd, "nsock = %d", httpd->nsock);
217 close(httpd->sock[idx]);
218 httpd->nsock--;
219 bozowarn(httpd, "nsock now = %d", httpd->nsock);
220 /* no sockets left */
221 if (httpd->nsock == 0)
222 exit(0);
223 /* last socket closed is the easy case */
224 if (httpd->nsock != idx) {
225 memmove(&httpd->fds[idx], &httpd->fds[idx+1],
226 (httpd->nsock - idx) * sizeof(*httpd->fds));
227 memmove(&httpd->sock[idx], &httpd->sock[idx+1],
228 (httpd->nsock - idx) * sizeof(*httpd->sock));
229 }
230
231 return 1;
232 }
233
234 /*
235 * the parent never returns from this function, only children that
236 * are ready to run... XXXMRG - still true in fork-lesser bozo?
237 */
238 int
239 bozo_daemon_fork(bozohttpd_t *httpd)
240 {
241 int i;
242
243 debug((httpd, DEBUG_FAT, "%s: pid %u request_times %d",
244 __func__, getpid(),
245 httpd->request_times));
246 /* if we've handled 5 files, exit and let someone else work */
247 if (httpd->request_times > 5 ||
248 (httpd->background == 2 && httpd->request_times > 0))
249 _exit(0);
250
251 #if 1
252 if (httpd->request_times > 0)
253 _exit(0);
254 #endif
255
256 while (httpd->background) {
257 struct sockaddr_storage ss;
258 socklen_t slen;
259 int fd;
260
261 if (httpd->nsock == 0)
262 exit(0);
263
264 /*
265 * wait for a connection, then fork() and return NULL in
266 * the parent, who will come back here waiting for another
267 * connection. read the request in in the child, and return
268 * it, for processing.
269 */
270 again:
271 if (poll(httpd->fds, (unsigned)httpd->nsock, INFTIM) == -1) {
272 /* fail on programmer errors */
273 if (errno == EFAULT ||
274 errno == EINVAL)
275 bozoerr(httpd, 1, "poll: %s",
276 strerror(errno));
277
278 /* sleep on some temporary kernel failures */
279 if (errno == ENOMEM ||
280 errno == EAGAIN)
281 sleep(1);
282
283 goto again;
284 }
285
286 for (i = 0; i < httpd->nsock; i++) {
287 if (daemon_poll_err(httpd, fd, i))
288 break;
289 if (httpd->fds[i].revents == 0)
290 continue;
291
292 slen = sizeof(ss);
293 fd = accept(httpd->fds[i].fd,
294 (struct sockaddr *)(void *)&ss, &slen);
295 if (fd == -1) {
296 if (errno == EFAULT ||
297 errno == EINVAL)
298 bozoerr(httpd, 1, "accept: %s",
299 strerror(errno));
300
301 if (errno == ENOMEM ||
302 errno == EAGAIN)
303 sleep(1);
304
305 continue;
306 }
307
308 #if 0
309 /*
310 * This code doesn't work. It interacts very poorly
311 * with ~user translation and needs to be fixed.
312 */
313 if (httpd->request_times > 0) {
314 daemon_runchild(httpd, fd);
315 return 0;
316 }
317 #endif
318
319 switch (fork()) {
320 case -1: /* eep, failure */
321 bozowarn(httpd, "fork() failed, sleeping for "
322 "10 seconds: %s", strerror(errno));
323 close(fd);
324 sleep(10);
325 break;
326
327 case 0: /* child */
328 daemon_runchild(httpd, fd);
329 return 0;
330
331 default: /* parent */
332 close(fd);
333 break;
334 }
335 }
336 }
337 return 0;
338 }
339
340 #endif /* NO_DAEMON_MODE */
341