xref: /lighttpd1.4/src/gw_backend.c (revision 463c60ce)
1 /*
2  * gw_backend - gateway backend code shared by dynamic socket backends
3  *
4  * Copyright(c) 2017 Glenn Strauss gstrauss()gluelogic.com  All rights reserved
5  * License: BSD 3-clause (same as lighttpd)
6  */
7 #include "first.h"
8 
9 #include "gw_backend.h"
10 
11 #include <sys/types.h>
12 #include <sys/stat.h>
13 #include "sys-socket.h"
14 #ifdef HAVE_SYS_UIO_H
15 #include <sys/uio.h>
16 #endif
17 #ifdef HAVE_SYS_WAIT_H
18 #include <sys/wait.h>
19 #endif
20 
21 #include <errno.h>
22 #include <fcntl.h>
23 #include <limits.h>
24 #include <stdint.h>
25 #include <stdlib.h>
26 #include <signal.h>
27 #include <string.h>
28 #include <unistd.h>
29 
30 #include "base.h"
31 #include "algo_md.h"
32 #include "array.h"
33 #include "buffer.h"
34 #include "chunk.h"
35 #include "fdevent.h"
36 #include "http_header.h"
37 #include "log.h"
38 #include "sock_addr.h"
39 
40 
41 
42 
43 __attribute_noinline__
44 static int * gw_status_get_counter(gw_host *host, gw_proc *proc, const char *tag, size_t tlen) {
45     /*(At the cost of some memory, could prepare strings for host and for proc
46      * so that here we would copy ready made string for proc (or if NULL,
47      * for host), and then append tag to produce key)*/
48     char label[288];
49     size_t llen = sizeof("gw.backend.")-1, len;
50     memcpy(label, "gw.backend.", llen);
51 
52     len = buffer_clen(host->id);
53     if (len) {
54         force_assert(len < sizeof(label) - llen);
55         memcpy(label+llen, host->id->ptr, len);
56         llen += len;
57     }
58 
59     if (proc) {
60         force_assert(llen < sizeof(label) - (LI_ITOSTRING_LENGTH + 1));
61         label[llen++] = '.';
62         len = li_utostrn(label+llen, LI_ITOSTRING_LENGTH, proc->id);
63         llen += len;
64     }
65 
66     force_assert(tlen < sizeof(label) - llen);
67     memcpy(label+llen, tag, tlen);
68     llen += tlen;
69     label[llen] = '\0';
70 
71     return plugin_stats_get_ptr(label, llen);
72 }
73 
74 static void gw_proc_tag_inc(gw_host *host, gw_proc *proc, const char *tag, size_t len) {
75     ++(*gw_status_get_counter(host, proc, tag, len));
76 }
77 
78 static void gw_proc_connected_inc(gw_host *host, gw_proc *proc) {
79     UNUSED(host);
80     ++(*proc->stats_connected); /* "gw.backend...connected" */
81 }
82 
83 static void gw_proc_load_inc(gw_host *host, gw_proc *proc) {
84     *proc->stats_load = ++proc->load; /* "gw.backend...load" */
85     ++(*host->stats_global_active); /* "gw.active-requests" */
86 }
87 
88 static void gw_proc_load_dec(gw_host *host, gw_proc *proc) {
89     *proc->stats_load = --proc->load; /* "gw.backend...load" */
90     --(*host->stats_global_active); /* "gw.active-requests" */
91 }
92 
93 static void gw_host_assign(gw_host *host) {
94     *host->stats_load = ++host->load; /* "gw.backend...load" */
95 }
96 
97 static void gw_host_reset(gw_host *host) {
98     *host->stats_load = --host->load; /* "gw.backend...load" */
99 }
100 
101 static void gw_status_init_proc(gw_host *host, gw_proc *proc) {
102     *gw_status_get_counter(host, proc, CONST_STR_LEN(".disabled")) = 0;
103     *gw_status_get_counter(host, proc, CONST_STR_LEN(".died")) = 0;
104     *gw_status_get_counter(host, proc, CONST_STR_LEN(".overloaded")) = 0;
105     proc->stats_connected =
106       gw_status_get_counter(host, proc, CONST_STR_LEN(".connected"));
107     *proc->stats_connected = 0;
108     proc->stats_load =
109       gw_status_get_counter(host, proc, CONST_STR_LEN(".load"));
110     *proc->stats_load = 0;
111 }
112 
113 static void gw_status_init_host(gw_host *host) {
114     host->stats_load =
115       gw_status_get_counter(host, NULL, CONST_STR_LEN(".load"));
116     *host->stats_load = 0;
117     host->stats_global_active =
118       plugin_stats_get_ptr("gw.active-requests",sizeof("gw.active-requests")-1);
119 }
120 
121 
122 
123 
124 __attribute_cold__
125 static void gw_proc_set_state(gw_host *host, gw_proc *proc, int state) {
126     if ((int)proc->state == state) return;
127     if (proc->state == PROC_STATE_RUNNING) {
128         --host->active_procs;
129     } else if (state == PROC_STATE_RUNNING) {
130         ++host->active_procs;
131     }
132     proc->state = state;
133 }
134 
135 
136 __attribute_cold__
137 __attribute_noinline__
138 static void gw_proc_init_portpath(gw_host *host, gw_proc *proc) {
139     if (!host->unixsocket) {
140         proc->port = host->port + proc->id;
141         return;
142     }
143 
144     if (!proc->unixsocket)
145         proc->unixsocket = buffer_init();
146 
147     if (!host->bin_path)
148         buffer_copy_buffer(proc->unixsocket, host->unixsocket);
149     else {
150         buffer_clear(proc->unixsocket);
151         buffer_append_str2(proc->unixsocket, BUF_PTR_LEN(host->unixsocket),
152                                              CONST_STR_LEN("-"));
153         buffer_append_int(proc->unixsocket, proc->id);
154     }
155 }
156 
157 __attribute_cold__
158 __attribute_noinline__
159 __attribute_returns_nonnull__
160 static gw_proc *gw_proc_init(gw_host *host) {
161     gw_proc *proc = calloc(1, sizeof(*proc));
162     force_assert(proc);
163 
164     /*proc->unixsocket = buffer_init();*//*(init on demand)*/
165     proc->connection_name = buffer_init();
166 
167     proc->prev = NULL;
168     proc->next = NULL;
169     proc->state = PROC_STATE_DIED;
170 
171     proc->id = host->max_id++;
172     gw_status_init_proc(host, proc); /*(proc->id must be set)*/
173     gw_proc_init_portpath(host, proc);
174 
175     return proc;
176 }
177 
178 static void gw_proc_free(gw_proc *proc) {
179     if (!proc) return;
180 
181     gw_proc_free(proc->next);
182 
183     buffer_free(proc->unixsocket);
184     buffer_free(proc->connection_name);
185     free(proc->saddr);
186 
187     free(proc);
188 }
189 
190 __attribute_malloc__
191 __attribute_returns_nonnull__
192 static gw_host *gw_host_init(void) {
193     gw_host *f = calloc(1, sizeof(*f));
194     force_assert(f);
195     return f;
196 }
197 
198 static void gw_host_free(gw_host *h) {
199     if (!h) return;
200     if (h->refcount) {
201         --h->refcount;
202         return;
203     }
204 
205     gw_proc_free(h->first);
206     gw_proc_free(h->unused_procs);
207 
208     for (uint32_t i = 0; i < h->args.used; ++i) free(h->args.ptr[i]);
209     free(h->args.ptr);
210     free(h);
211 }
212 
213 __attribute_malloc__
214 __attribute_returns_nonnull__
215 static gw_exts *gw_extensions_init(void) {
216     gw_exts *f = calloc(1, sizeof(*f));
217     force_assert(f);
218     return f;
219 }
220 
221 static void gw_extensions_free(gw_exts *f) {
222     if (!f) return;
223     for (uint32_t i = 0; i < f->used; ++i) {
224         gw_extension *fe = f->exts+i;
225         for (uint32_t j = 0; j < fe->used; ++j) {
226             gw_host_free(fe->hosts[j]);
227         }
228         free(fe->hosts);
229     }
230     free(f->exts);
231     free(f);
232 }
233 
234 static int gw_extension_insert(gw_exts *ext, const buffer *key, gw_host *fh) {
235     gw_extension *fe = NULL;
236     for (uint32_t i = 0; i < ext->used; ++i) {
237         if (buffer_is_equal(key, &ext->exts[i].key)) {
238             fe = ext->exts+i;
239             break;
240         }
241     }
242 
243     if (NULL == fe) {
244         if (ext->used == ext->size) {
245             ext->size += 8;
246             ext->exts = realloc(ext->exts, ext->size * sizeof(gw_extension));
247             force_assert(ext->exts);
248             memset(ext->exts + ext->used, 0, 8 * sizeof(gw_extension));
249         }
250         fe = ext->exts + ext->used++;
251         fe->last_used_ndx = -1;
252         buffer *b;
253         *(const buffer **)&b = &fe->key;
254         memcpy(b, key, sizeof(buffer)); /*(copy; not later free'd)*/
255     }
256 
257     if (fe->size == fe->used) {
258         fe->size += 4;
259         fe->hosts = realloc(fe->hosts, fe->size * sizeof(*(fe->hosts)));
260         force_assert(fe->hosts);
261     }
262 
263     fe->hosts[fe->used++] = fh;
264     return 0;
265 }
266 
267 static void gw_proc_connect_success(gw_host *host, gw_proc *proc, int debug, request_st * const r) {
268     gw_proc_connected_inc(host, proc); /*(".connected")*/
269     proc->last_used = log_monotonic_secs;
270 
271     if (debug) {
272         log_error(r->conf.errh, __FILE__, __LINE__,
273           "got proc: pid: %d socket: %s load: %d",
274           proc->pid, proc->connection_name->ptr, proc->load);
275     }
276 }
277 
278 __attribute_cold__
279 static void gw_proc_connect_error(request_st * const r, gw_host *host, gw_proc *proc, pid_t pid, int errnum, int debug) {
280     const unix_time64_t cur_ts = log_monotonic_secs;
281     log_error_st * const errh = r->conf.errh;
282     errno = errnum; /*(for log_perror())*/
283     log_perror(errh, __FILE__, __LINE__,
284       "establishing connection failed: socket: %s", proc->connection_name->ptr);
285 
286     if (!proc->is_local) {
287         proc->disabled_until = cur_ts + host->disable_time;
288         gw_proc_set_state(host, proc, PROC_STATE_OVERLOADED);
289     }
290     else if (proc->pid == pid && proc->state == PROC_STATE_RUNNING) {
291         /* several requests from lighttpd might reference the same proc
292          *
293          * Only one of them should mark the proc
294          * and all other ones should just take a new one.
295          *
296          * If a new proc was started with the old struct, this might
297          * otherwise lead to marking a perfectly good proc as dead
298          */
299         log_error(errh, __FILE__, __LINE__,
300           "backend error; we'll disable for %d"
301           "secs and send the request to another backend instead:"
302           "load: %d", host->disable_time, host->load);
303         if (EAGAIN == errnum) {
304             /* - EAGAIN: cool down the backend; it is overloaded */
305           #ifdef __linux__
306             log_error(errh, __FILE__, __LINE__,
307               "If this happened on Linux: You have run out of local ports. "
308               "Check the manual, section Performance how to handle this.");
309           #endif
310             if (debug) {
311                 log_error(errh, __FILE__, __LINE__,
312                   "This means that you have more incoming requests than your "
313                   "FastCGI backend can handle in parallel.  It might help to "
314                   "spawn more FastCGI backends or PHP children; if not, "
315                   "decrease server.max-connections.  The load for this FastCGI "
316                   "backend %s is %d", proc->connection_name->ptr, proc->load);
317             }
318             proc->disabled_until = cur_ts + host->disable_time;
319             gw_proc_set_state(host, proc, PROC_STATE_OVERLOADED);
320         }
321         else {
322             /* we got a hard error from the backend like
323              * - ECONNREFUSED for tcp-ip sockets
324              * - ENOENT for unix-domain-sockets
325              */
326           #if 0
327             gw_proc_set_state(host, proc, PROC_STATE_DIED_WAIT_FOR_PID);
328           #else  /* treat as overloaded (future: unless we send kill() signal)*/
329             proc->disabled_until = cur_ts + host->disable_time;
330             gw_proc_set_state(host, proc, PROC_STATE_OVERLOADED);
331           #endif
332         }
333     }
334 
335     if (EAGAIN == errnum) {
336         gw_proc_tag_inc(host, proc, CONST_STR_LEN(".overloaded"));
337     }
338     else {
339         gw_proc_tag_inc(host, proc, CONST_STR_LEN(".died"));
340     }
341 }
342 
343 static void gw_proc_release(gw_host *host, gw_proc *proc, int debug, log_error_st *errh) {
344     gw_proc_load_dec(host, proc);
345 
346     if (debug) {
347         log_error(errh, __FILE__, __LINE__,
348           "released proc: pid: %d socket: %s load: %u",
349           proc->pid, proc->connection_name->ptr, proc->load);
350     }
351 }
352 
353 __attribute_cold__
354 static void gw_proc_check_enable(gw_host * const host, gw_proc * const proc, log_error_st * const errh) {
355     if (log_monotonic_secs <= proc->disabled_until) return;
356     if (proc->state != PROC_STATE_OVERLOADED) return;
357 
358     gw_proc_set_state(host, proc, PROC_STATE_RUNNING);
359 
360     log_error(errh, __FILE__, __LINE__,
361       "gw-server re-enabled: %s %s %hu %s",
362       proc->connection_name->ptr,
363       host->host ? host->host->ptr : "", host->port,
364       host->unixsocket ? host->unixsocket->ptr : "");
365 }
366 
367 __attribute_cold__
368 static void gw_proc_waitpid_log(const gw_host * const host, const gw_proc * const proc, log_error_st * const errh, const int status) {
369     if (WIFEXITED(status)) {
370         if (proc->state != PROC_STATE_KILLED) {
371             log_error(errh, __FILE__, __LINE__,
372               "child exited: %d %s",
373               WEXITSTATUS(status), proc->connection_name->ptr);
374         }
375     } else if (WIFSIGNALED(status)) {
376         if (WTERMSIG(status) != SIGTERM && WTERMSIG(status) != SIGINT
377             && WTERMSIG(status) != host->kill_signal) {
378             log_error(errh, __FILE__, __LINE__,
379               "child signalled: %d", WTERMSIG(status));
380         }
381     } else {
382         log_error(errh, __FILE__, __LINE__,
383           "child died somehow: %d", status);
384     }
385 }
386 
387 static int gw_proc_waitpid(gw_host *host, gw_proc *proc, log_error_st *errh) {
388     int rc, status;
389 
390     if (!proc->is_local) return 0;
391     if (proc->pid <= 0) return 0;
392 
393     rc = fdevent_waitpid(proc->pid, &status, 1);
394     if (0 == rc) return 0; /* child still running */
395 
396     /* child terminated */
397     if (-1 == rc) {
398         /* EINVAL or ECHILD no child processes */
399         /* should not happen; someone else has cleaned up for us */
400         log_perror(errh, __FILE__, __LINE__,
401           "pid %d %d not found", proc->pid, proc->state);
402     }
403     else {
404         gw_proc_waitpid_log(host, proc, errh, status);
405     }
406 
407     proc->pid = 0;
408     if (proc->state != PROC_STATE_KILLED)
409         proc->disabled_until = log_monotonic_secs;
410     gw_proc_set_state(host, proc, PROC_STATE_DIED);
411     return 1;
412 }
413 
414 __attribute_cold__
415 static int gw_proc_sockaddr_init(gw_host * const host, gw_proc * const proc, log_error_st * const errh) {
416     sock_addr addr;
417     socklen_t addrlen;
418 
419     if (proc->unixsocket) {
420         if (1 != sock_addr_from_str_hints(&addr,&addrlen,proc->unixsocket->ptr,
421                                           AF_UNIX, 0, errh)) {
422             errno = EINVAL;
423             return -1;
424         }
425         buffer_clear(proc->connection_name);
426         buffer_append_str2(proc->connection_name,
427                            CONST_STR_LEN("unix:"),
428                            BUF_PTR_LEN(proc->unixsocket));
429     }
430     else {
431       #ifdef __COVERITY__
432         force_assert(host->host); /*(not NULL if !host->unixsocket)*/
433       #endif
434         /*(note: name resolution here is *blocking* if IP string not supplied)*/
435         if (1 != sock_addr_from_str_hints(&addr, &addrlen, host->host->ptr,
436                                           0, proc->port, errh)) {
437             errno = EINVAL;
438             return -1;
439         }
440         else if (host->host->size) {
441             /*(skip if constant string set in gw_set_defaults_backend())*/
442             /* overwrite host->host buffer with IP addr string so that
443              * any further use of gw_host does not block on DNS lookup */
444             buffer *h;
445             *(const buffer **)&h = host->host;
446             sock_addr_inet_ntop_copy_buffer(h, &addr);
447             host->family = sock_addr_get_family(&addr);
448         }
449         buffer_clear(proc->connection_name);
450         buffer_append_str3(proc->connection_name,
451                            CONST_STR_LEN("tcp:"),
452                            BUF_PTR_LEN(host->host),
453                            CONST_STR_LEN(":"));
454         buffer_append_int(proc->connection_name, proc->port);
455     }
456 
457     if (NULL != proc->saddr && proc->saddrlen < addrlen) {
458         free(proc->saddr);
459         proc->saddr = NULL;
460     }
461     if (NULL == proc->saddr) {
462         proc->saddr = (struct sockaddr *)malloc(addrlen);
463         force_assert(proc->saddr);
464     }
465     proc->saddrlen = addrlen;
466     memcpy(proc->saddr, &addr, addrlen);
467     return 0;
468 }
469 
470 static int env_add(char_array *env, const char *key, size_t key_len, const char *val, size_t val_len) {
471     char *dst;
472 
473     if (!key || !val) return -1;
474 
475     dst = malloc(key_len + val_len + 3);
476     force_assert(dst);
477     memcpy(dst, key, key_len);
478     dst[key_len] = '=';
479     memcpy(dst + key_len + 1, val, val_len + 1); /* add the \0 from the value */
480 
481     for (uint32_t i = 0; i < env->used; ++i) {
482       #ifdef __COVERITY__
483         force_assert(env->ptr); /*(non-NULL if env->used != 0)*/
484       #endif
485         if (0 == strncmp(dst, env->ptr[i], key_len + 1)) {
486             free(env->ptr[i]);
487             env->ptr[i] = dst;
488             return 0;
489         }
490     }
491 
492     if (env->size <= env->used + 1) {
493         env->size += 16;
494         env->ptr = realloc(env->ptr, env->size * sizeof(*env->ptr));
495         force_assert(env->ptr);
496     }
497 
498   #ifdef __COVERITY__
499     force_assert(env->ptr); /*(non-NULL if env->used != 0; guaranteed above)*/
500   #endif
501     env->ptr[env->used++] = dst;
502 
503     return 0;
504 }
505 
506 __attribute_cold__
507 static int gw_spawn_connection(gw_host * const host, gw_proc * const proc, log_error_st * const errh, int debug) {
508     int gw_fd;
509     int status;
510 
511     if (debug) {
512         log_error(errh, __FILE__, __LINE__,
513           "new proc, socket: %hu %s",
514           proc->port, proc->unixsocket ? proc->unixsocket->ptr : "");
515     }
516 
517     gw_fd = fdevent_socket_cloexec(proc->saddr->sa_family, SOCK_STREAM, 0);
518     if (-1 == gw_fd) {
519         log_perror(errh, __FILE__, __LINE__, "socket()");
520         return -1;
521     }
522 
523     do {
524         status = connect(gw_fd, proc->saddr, proc->saddrlen);
525     } while (-1 == status && errno == EINTR);
526 
527     if (-1 == status && errno != ENOENT && proc->unixsocket) {
528         log_perror(errh, __FILE__, __LINE__,
529           "connect %s", proc->unixsocket->ptr);
530         unlink(proc->unixsocket->ptr);
531     }
532 
533     close(gw_fd);
534 
535     if (-1 == status) {
536         /* server is not up, spawn it  */
537         char_array env;
538         uint32_t i;
539 
540         /* reopen socket */
541         gw_fd = fdevent_socket_cloexec(proc->saddr->sa_family, SOCK_STREAM, 0);
542         if (-1 == gw_fd) {
543             log_perror(errh, __FILE__, __LINE__, "socket()");
544             return -1;
545         }
546 
547         if (fdevent_set_so_reuseaddr(gw_fd, 1) < 0) {
548             log_perror(errh, __FILE__, __LINE__, "socketsockopt()");
549             close(gw_fd);
550             return -1;
551         }
552 
553         /* create socket */
554         if (-1 == bind(gw_fd, proc->saddr, proc->saddrlen)) {
555             log_perror(errh, __FILE__, __LINE__,
556               "bind failed for: %s", proc->connection_name->ptr);
557             close(gw_fd);
558             return -1;
559         }
560 
561         if (-1 == listen(gw_fd, host->listen_backlog)) {
562             log_perror(errh, __FILE__, __LINE__, "listen()");
563             close(gw_fd);
564             return -1;
565         }
566 
567         {
568             /* create environment */
569             env.ptr = NULL;
570             env.size = 0;
571             env.used = 0;
572 
573             /* build clean environment */
574             if (host->bin_env_copy && host->bin_env_copy->used) {
575                 for (i = 0; i < host->bin_env_copy->used; ++i) {
576                     data_string *ds=(data_string *)host->bin_env_copy->data[i];
577                     char *ge;
578 
579                     if (NULL != (ge = getenv(ds->value.ptr))) {
580                         env_add(&env, BUF_PTR_LEN(&ds->value), ge, strlen(ge));
581                     }
582                 }
583             } else {
584                 char ** const e = fdevent_environ();
585                 for (i = 0; e[i]; ++i) {
586                     char *eq;
587 
588                     if (NULL != (eq = strchr(e[i], '='))) {
589                         env_add(&env, e[i], eq - e[i], eq+1, strlen(eq+1));
590                     }
591                 }
592             }
593 
594             /* create environment */
595             if (host->bin_env) {
596                 for (i = 0; i < host->bin_env->used; ++i) {
597                     data_string *ds = (data_string *)host->bin_env->data[i];
598                     env_add(&env, BUF_PTR_LEN(&ds->key),
599                                   BUF_PTR_LEN(&ds->value));
600                 }
601             }
602 
603             for (i = 0; i < env.used; ++i) {
604                 /* search for PHP_FCGI_CHILDREN */
605                 if (0 == strncmp(env.ptr[i], "PHP_FCGI_CHILDREN=",
606                                       sizeof("PHP_FCGI_CHILDREN=")-1)) {
607                     break;
608                 }
609             }
610 
611             /* not found, add a default */
612             if (i == env.used) {
613                 env_add(&env, CONST_STR_LEN("PHP_FCGI_CHILDREN"),
614                               CONST_STR_LEN("1"));
615             }
616 
617             env.ptr[env.used] = NULL;
618         }
619 
620         int dfd = fdevent_open_dirname(host->args.ptr[0], 1);/*permit symlinks*/
621         if (-1 == dfd) {
622             log_perror(errh, __FILE__, __LINE__,
623               "open dirname failed: %s", host->args.ptr[0]);
624         }
625 
626         /*(FCGI_LISTENSOCK_FILENO == STDIN_FILENO == 0)*/
627         proc->pid = (dfd >= 0)
628           ? fdevent_fork_execve(host->args.ptr[0], host->args.ptr,
629                                 env.ptr, gw_fd, -1, -1, dfd)
630           : -1;
631 
632         for (i = 0; i < env.used; ++i) free(env.ptr[i]);
633         free(env.ptr);
634         if (-1 != dfd) close(dfd);
635         close(gw_fd);
636 
637         if (-1 == proc->pid) {
638             log_error(errh, __FILE__, __LINE__,
639               "gw-backend failed to start: %s", host->bin_path->ptr);
640             proc->pid = 0;
641             proc->disabled_until = log_monotonic_secs;
642             return -1;
643         }
644 
645         /* register process */
646         proc->last_used = log_monotonic_secs;
647         proc->is_local = 1;
648 
649         /* wait */
650         struct timeval tv = { 0, 1000 };
651         select(0, NULL, NULL, NULL, &tv);
652 
653         if (0 != gw_proc_waitpid(host, proc, errh)) {
654             log_error(errh, __FILE__, __LINE__,
655               "gw-backend failed to start: %s", host->bin_path->ptr);
656             log_error(errh, __FILE__, __LINE__,
657               "If you're trying to run your app as a FastCGI backend, make "
658               "sure you're using the FastCGI-enabled version.  If this is PHP "
659               "on Gentoo, add 'fastcgi' to the USE flags.  If this is PHP, try "
660               "removing the bytecode caches for now and try again.");
661             return -1;
662         }
663     } else {
664         proc->is_local = 0;
665         proc->pid = 0;
666 
667         if (debug) {
668             log_error(errh, __FILE__, __LINE__,
669               "(debug) socket is already used; won't spawn: %s",
670               proc->connection_name->ptr);
671         }
672     }
673 
674     gw_proc_set_state(host, proc, PROC_STATE_RUNNING);
675     return 0;
676 }
677 
678 __attribute_cold__
679 static void gw_proc_spawn(gw_host * const host, log_error_st * const errh, const int debug) {
680     gw_proc *proc;
681     for (proc = host->unused_procs; proc; proc = proc->next) {
682         /* (proc->pid <= 0 indicates PROC_STATE_DIED, not PROC_STATE_KILLED) */
683         if (proc->pid > 0) continue;
684         /* (do not attempt to spawn another proc if a proc just exited) */
685         if (proc->disabled_until >= log_monotonic_secs) return;
686         break;
687     }
688     if (proc) {
689         if (proc == host->unused_procs)
690             host->unused_procs = proc->next;
691         else
692             proc->prev->next = proc->next;
693 
694         if (proc->next) {
695             proc->next->prev = proc->prev;
696             proc->next = NULL;
697         }
698 
699         proc->prev = NULL;
700         gw_proc_init_portpath(host, proc);
701     } else {
702         proc = gw_proc_init(host);
703     }
704 
705     if (0 != gw_proc_sockaddr_init(host, proc, errh)) {
706         /*(should not happen if host->host validated at startup,
707          * and translated from name to IP address at startup)*/
708         log_error(errh, __FILE__, __LINE__,
709           "ERROR: spawning backend failed.");
710         if (proc->id == host->max_id-1) --host->max_id;
711         gw_proc_free(proc);
712     } else if (gw_spawn_connection(host, proc, errh, debug)) {
713         log_error(errh, __FILE__, __LINE__,
714           "ERROR: spawning backend failed.");
715         proc->next = host->unused_procs;
716         if (host->unused_procs)
717             host->unused_procs->prev = proc;
718         host->unused_procs = proc;
719     } else {
720         proc->next = host->first;
721         if (host->first)
722             host->first->prev = proc;
723         host->first = proc;
724         ++host->num_procs;
725     }
726 }
727 
728 __attribute_cold__
729 static void gw_proc_kill(gw_host *host, gw_proc *proc) {
730     if (proc->next) proc->next->prev = proc->prev;
731     if (proc->prev) proc->prev->next = proc->next;
732     else host->first = proc->next;
733     --host->num_procs;
734 
735     proc->prev = NULL;
736     proc->next = host->unused_procs;
737     proc->disabled_until = 0;
738 
739     if (host->unused_procs)
740         host->unused_procs->prev = proc;
741     host->unused_procs = proc;
742 
743     kill(proc->pid, host->kill_signal);
744 
745     gw_proc_set_state(host, proc, PROC_STATE_KILLED);
746 }
747 
748 __attribute_pure__
749 static gw_host * unixsocket_is_dup(gw_plugin_data *p, const buffer *unixsocket) {
750     if (NULL == p->cvlist) return NULL;
751     /* (init i to 0 if global context; to 1 to skip empty global context) */
752     for (int i = !p->cvlist[0].v.u2[1], used = p->nconfig; i < used; ++i) {
753         config_plugin_value_t *cpv = p->cvlist + p->cvlist[i].v.u2[0];
754         gw_plugin_config *conf = NULL;
755         for (; -1 != cpv->k_id; ++cpv) {
756             switch (cpv->k_id) {
757               case 0: /* xxxxx.server */
758                 if (cpv->vtype == T_CONFIG_LOCAL) conf = cpv->v.v;
759                 break;
760               default:
761                 break;
762             }
763         }
764 
765         if (NULL == conf || NULL == conf->exts) continue;
766 
767         gw_exts *exts = conf->exts;
768         for (uint32_t j = 0; j < exts->used; ++j) {
769             gw_extension *ex = exts->exts+j;
770             for (uint32_t n = 0; n < ex->used; ++n) {
771                 gw_host *host = ex->hosts[n];
772                 if (host->unixsocket
773                     && buffer_is_equal(host->unixsocket, unixsocket)
774                     && host->bin_path)
775                     return host;
776             }
777         }
778     }
779 
780     return NULL;
781 }
782 
783 static int parse_binpath(char_array *env, const buffer *b) {
784     char *start = b->ptr;
785     char c;
786     /* search for spaces */
787     for (size_t i = 0, used = buffer_clen(b); i < used; ++i) {
788         switch(b->ptr[i]) {
789         case ' ':
790         case '\t':
791             /* a WS, stop here and copy the argument */
792 
793             if (env->size == env->used) {
794                 env->size += 16;
795                 env->ptr = realloc(env->ptr, env->size * sizeof(*env->ptr));
796                 force_assert(env->ptr);
797             }
798 
799             c = b->ptr[i];
800             b->ptr[i] = '\0';
801             env->ptr[env->used++] = strdup(start);
802             b->ptr[i] = c;
803 
804             start = b->ptr + i + 1;
805             break;
806         default:
807             break;
808         }
809     }
810 
811     if (env->size == env->used) { /*need one extra for terminating NULL*/
812         env->size += 16;
813         env->ptr = realloc(env->ptr, env->size * sizeof(*env->ptr));
814     }
815 
816     /* the rest */
817     env->ptr[env->used++] = strdup(start);
818 
819     if (env->size == env->used) { /*need one extra for terminating NULL*/
820         env->size += 16;
821         env->ptr = realloc(env->ptr, env->size * sizeof(*env->ptr));
822     }
823 
824     /* terminate */
825     env->ptr[env->used++] = NULL;
826 
827     return 0;
828 }
829 
830 enum {
831   GW_BALANCE_LEAST_CONNECTION,
832   GW_BALANCE_RR,
833   GW_BALANCE_HASH,
834   GW_BALANCE_STICKY
835 };
836 
837 __attribute_noinline__
838 __attribute_pure__
839 static uint32_t
840 gw_hash(const char *str, const uint32_t len, uint32_t hash)
841 {
842     return djbhash(str, len, hash);
843 }
844 
845 static gw_host * gw_host_get(request_st * const r, gw_extension *extension, int balance, int debug) {
846     int ndx = -1;
847     const int ext_used = (int)extension->used;
848 
849     if (ext_used <= 1) {
850         if (1 == ext_used && extension->hosts[0]->active_procs > 0)
851             ndx = 0;
852     }
853     else {
854      /*const char *balancing = "";*/
855      switch(balance) {
856       case GW_BALANCE_HASH:
857        { /* hash balancing */
858         const uint32_t base_hash =
859           gw_hash(BUF_PTR_LEN(&r->uri.authority),
860                   gw_hash(BUF_PTR_LEN(&r->uri.path), DJBHASH_INIT));
861         uint32_t last_max = UINT32_MAX;
862         for (int k = 0; k < ext_used; ++k) {
863             const gw_host * const host = extension->hosts[k];
864             if (0 == host->active_procs) continue;
865             const uint32_t cur_max = base_hash ^ host->gw_hash;
866           #if 0
867             if (debug) {
868                 log_error(r->conf.errh, __FILE__, __LINE__,
869                   "proxy - election: %s %s %s %u", r->uri.path.ptr,
870                   host->host ? host->host->ptr : "",
871                   r->uri.authority.ptr, cur_max);
872             }
873           #endif
874             if (last_max < cur_max || last_max == UINT32_MAX) {
875                 last_max = cur_max;
876                 ndx = k;
877             }
878         }
879         /*balancing = "hash";*/
880         break;
881        }
882       case GW_BALANCE_LEAST_CONNECTION:
883        { /* fair balancing */
884         for (int k = 0, max_usage = INT_MAX; k < ext_used; ++k) {
885             const gw_host * const host = extension->hosts[k];
886             if (0 == host->active_procs) continue;
887             if (host->load < max_usage) {
888                 max_usage = host->load;
889                 ndx = k;
890             }
891         }
892         /*balancing = "least connection";*/
893         break;
894        }
895       case GW_BALANCE_RR:
896        { /* round robin */
897         const gw_host *host = extension->hosts[0];
898 
899         /* Use last_used_ndx from first host in list */
900         int k = extension->last_used_ndx;
901         ndx = k + 1; /* use next host after the last one */
902         if (ndx < 0) ndx = 0;
903 
904         /* Search first active host after last_used_ndx */
905         while (ndx < ext_used
906                && 0 == (host = extension->hosts[ndx])->active_procs) ++ndx;
907 
908         if (ndx >= ext_used) {
909             /* didn't find a higher id, wrap to the start */
910             for (ndx = 0; ndx <= (int) k; ++ndx) {
911                 host = extension->hosts[ndx];
912                 if (0 != host->active_procs) break;
913             }
914 
915             /* No active host found */
916             if (0 == host->active_procs) ndx = -1;
917         }
918 
919         /* Save new index for next round */
920         extension->last_used_ndx = ndx;
921 
922         /*balancing = "round-robin";*/
923         break;
924        }
925       case GW_BALANCE_STICKY:
926        { /* source sticky balancing */
927         const buffer * const dst_addr_buf = &r->con->dst_addr_buf;
928         const uint32_t base_hash =
929           gw_hash(BUF_PTR_LEN(dst_addr_buf), DJBHASH_INIT);
930         uint32_t last_max = UINT32_MAX;
931         for (int k = 0; k < ext_used; ++k) {
932             const gw_host * const host = extension->hosts[k];
933             if (0 == host->active_procs) continue;
934             const uint32_t cur_max = base_hash ^ host->gw_hash ^ host->port;
935           #if 0
936             if (debug) {
937                 log_error(r->conf.errh, __FILE__, __LINE__,
938                   "proxy - election: %s %s %hu %u", dst_addr_buf->ptr,
939                   host->host ? host->host->ptr : "",
940                   host->port, cur_max);
941             }
942           #endif
943             if (last_max < cur_max || last_max == UINT32_MAX) {
944                 last_max = cur_max;
945                 ndx = k;
946             }
947         }
948         /*balancing = "sticky";*/
949         break;
950        }
951       default:
952         break;
953      }
954      #if 0
955      if (debug) {
956         log_error(r->conf.errh, __FILE__, __LINE__,
957           "gw - balancing: %s, hosts: %d", balancing, ext_used);
958      }
959      #endif
960     }
961 
962     if (__builtin_expect( (-1 != ndx), 1)) {
963         /* found a server */
964 
965         if (debug) {
966             gw_host * const host = extension->hosts[ndx];
967             log_error(r->conf.errh, __FILE__, __LINE__,
968               "gw - found a host %s %hu",
969               host->host ? host->host->ptr : "", host->port);
970             return host;
971         }
972 
973         return extension->hosts[ndx];
974     } else if (0 == r->con->srv->srvconf.max_worker) {
975         /* special-case adaptive spawning and 0 == host->min_procs */
976         for (int k = 0; k < ext_used; ++k) {
977             gw_host * const host = extension->hosts[k];
978             if (0 == host->min_procs && 0 == host->num_procs && host->bin_path){
979                 gw_proc_spawn(host, r->con->srv->errh, debug);
980                 if (host->num_procs) return host;
981             }
982         }
983     }
984 
985     /* all hosts are down */
986     /* sorry, we don't have a server alive for this ext */
987     r->http_status = 503; /* Service Unavailable */
988     r->handler_module = NULL;
989 
990     /* only send the 'no handler' once */
991     if (!extension->note_is_sent) {
992         extension->note_is_sent = 1;
993         log_error(r->conf.errh, __FILE__, __LINE__,
994           "all handlers for %s?%.*s on %s are down.",
995           r->uri.path.ptr, BUFFER_INTLEN_PTR(&r->uri.query),
996           extension->key.ptr);
997     }
998 
999     return NULL;
1000 }
1001 
1002 static int gw_establish_connection(request_st * const r, gw_host *host, gw_proc *proc, pid_t pid, int gw_fd, int debug) {
1003     if (-1 == connect(gw_fd, proc->saddr, proc->saddrlen)) {
1004         int errnum = errno;
1005         if (errnum == EINPROGRESS || errnum == EALREADY || errnum == EINTR
1006             || (errnum == EAGAIN && host->unixsocket)) {
1007             if (debug > 2) {
1008                 log_error(r->conf.errh, __FILE__, __LINE__,
1009                   "connect delayed; will continue later: %s",
1010                   proc->connection_name->ptr);
1011             }
1012 
1013             return 1;
1014         } else {
1015             gw_proc_connect_error(r, host, proc, pid, errnum, debug);
1016             return -1;
1017         }
1018     }
1019 
1020     if (debug > 1) {
1021         log_error(r->conf.errh, __FILE__, __LINE__,
1022           "connect succeeded: %d", gw_fd);
1023     }
1024 
1025     return 0;
1026 }
1027 
1028 __attribute_cold__
1029 __attribute_noinline__
1030 static void gw_restart_dead_proc(gw_host * const host, log_error_st * const errh, const int debug, const int trigger, gw_proc * const proc) {
1031         switch (proc->state) {
1032         case PROC_STATE_RUNNING:
1033             break;
1034         case PROC_STATE_OVERLOADED:
1035             gw_proc_check_enable(host, proc, errh);
1036             break;
1037         case PROC_STATE_KILLED:
1038             if (trigger && ++proc->disabled_until > 4) {
1039                 int sig = (proc->disabled_until <= 8)
1040                   ? host->kill_signal
1041                   : proc->disabled_until <= 16 ? SIGTERM : SIGKILL;
1042                 kill(proc->pid, sig);
1043             }
1044             break;
1045         case PROC_STATE_DIED_WAIT_FOR_PID:
1046             /*(state should not happen in workers if server.max-worker > 0)*/
1047             /*(if PROC_STATE_DIED_WAIT_FOR_PID is used in future, might want
1048              * to save proc->disabled_until before gw_proc_waitpid() since
1049              * gw_proc_waitpid will set proc->disabled_until=log_monotonic_secs,
1050              * and so process will not be restarted below until one sec later)*/
1051             if (0 == gw_proc_waitpid(host, proc, errh)) {
1052                 gw_proc_check_enable(host, proc, errh);
1053             }
1054 
1055             if (proc->state != PROC_STATE_DIED) break;
1056             __attribute_fallthrough__/*(we have a dead proc now)*/
1057 
1058         case PROC_STATE_DIED:
1059             /* local procs get restarted by us,
1060              * remote ones hopefully by the admin */
1061 
1062             if (host->bin_path) {
1063                 /* we still have connections bound to this proc,
1064                  * let them terminate first */
1065                 if (proc->load != 0) break;
1066 
1067                 /* avoid spinning if child exits too quickly */
1068                 if (proc->disabled_until >= log_monotonic_secs) break;
1069 
1070                 /* restart the child */
1071 
1072                 if (debug) {
1073                     log_error(errh, __FILE__, __LINE__,
1074                       "--- gw spawning"
1075                       "\n\tsocket %s"
1076                       "\n\tcurrent: 1 / %u",
1077                       proc->connection_name->ptr, host->max_procs);
1078                 }
1079 
1080                 if (gw_spawn_connection(host, proc, errh, debug)) {
1081                     log_error(errh, __FILE__, __LINE__,
1082                       "ERROR: spawning gw failed.");
1083                 }
1084             } else {
1085                 gw_proc_check_enable(host, proc, errh);
1086             }
1087             break;
1088         }
1089 }
1090 
1091 static void gw_restart_dead_procs(gw_host * const host, log_error_st * const errh, const int debug, const int trigger) {
1092     for (gw_proc *proc = host->first; proc; proc = proc->next) {
1093         if (debug > 2) {
1094             log_error(errh, __FILE__, __LINE__,
1095               "proc: %s %d %d %d %d", proc->connection_name->ptr,
1096               proc->state, proc->is_local, proc->load, proc->pid);
1097         }
1098         if (proc->state != PROC_STATE_RUNNING)
1099             gw_restart_dead_proc(host, errh, debug, trigger, proc);
1100     }
1101 }
1102 
1103 
1104 
1105 
1106 #include "base.h"
1107 #include "response.h"
1108 
1109 
1110 /* ok, we need a prototype */
1111 static handler_t gw_handle_fdevent(void *ctx, int revents);
1112 static handler_t gw_process_fdevent(gw_handler_ctx *hctx, request_st *r, int revents);
1113 
1114 
1115 __attribute_returns_nonnull__
1116 static gw_handler_ctx * handler_ctx_init(size_t sz) {
1117     gw_handler_ctx *hctx = calloc(1, 0 == sz ? sizeof(*hctx) : sz);
1118     force_assert(hctx);
1119 
1120     /*hctx->response = chunk_buffer_acquire();*//*(allocated when needed)*/
1121 
1122     hctx->request_id = 0;
1123     hctx->gw_mode = GW_RESPONDER;
1124     hctx->state = GW_STATE_INIT;
1125     hctx->proc = NULL;
1126 
1127     hctx->fd = -1;
1128 
1129     hctx->reconnects = 0;
1130     hctx->send_content_body = 1;
1131 
1132     /*hctx->rb = chunkqueue_init();*//*(allocated when needed)*/
1133     chunkqueue_init(&hctx->wb);
1134     hctx->wb_reqlen = 0;
1135 
1136     return hctx;
1137 }
1138 
1139 static void handler_ctx_free(gw_handler_ctx *hctx) {
1140     /* caller MUST have called gw_backend_close(hctx, r) if necessary */
1141     if (hctx->handler_ctx_free) hctx->handler_ctx_free(hctx);
1142     chunk_buffer_release(hctx->response);
1143 
1144     if (hctx->rb) chunkqueue_free(hctx->rb);
1145     chunkqueue_reset(&hctx->wb);
1146 
1147     free(hctx);
1148 }
1149 
1150 static void handler_ctx_clear(gw_handler_ctx *hctx) {
1151     /* caller MUST have called gw_backend_close(hctx, r) if necessary */
1152 
1153     hctx->proc = NULL;
1154     hctx->host = NULL;
1155     hctx->ext  = NULL;
1156     /*hctx->ext_auth is intentionally preserved to flag prior authorizer*/
1157 
1158     hctx->gw_mode = GW_RESPONDER;
1159     hctx->state = GW_STATE_INIT;
1160     /*hctx->state_timestamp = 0;*//*(unused; left as-is)*/
1161 
1162     if (hctx->rb) chunkqueue_reset(hctx->rb);
1163     chunkqueue_reset(&hctx->wb);
1164     hctx->wb_reqlen = 0;
1165 
1166     if (hctx->response) buffer_clear(hctx->response);
1167 
1168     hctx->fd = -1;
1169     hctx->reconnects = 0;
1170     hctx->request_id = 0;
1171     hctx->send_content_body = 1;
1172 
1173     /*plugin_config conf;*//*(no need to reset for same request)*/
1174 
1175     /*hctx->r           = NULL;*//*(no need to reset for same request)*/
1176     /*hctx->plugin_data = NULL;*//*(no need to reset for same request)*/
1177 }
1178 
1179 
1180 void * gw_init(void) {
1181     return calloc(1, sizeof(gw_plugin_data));
1182 }
1183 
1184 
1185 void gw_plugin_config_free(gw_plugin_config *s) {
1186     gw_exts *exts = s->exts;
1187     if (exts) {
1188         for (uint32_t j = 0; j < exts->used; ++j) {
1189             gw_extension *ex = exts->exts+j;
1190             for (uint32_t n = 0; n < ex->used; ++n) {
1191                 gw_proc *proc;
1192                 gw_host *host = ex->hosts[n];
1193 
1194                 for (proc = host->first; proc; proc = proc->next) {
1195                     if (proc->pid > 0) {
1196                         kill(proc->pid, host->kill_signal);
1197                     }
1198 
1199                     if (proc->is_local && proc->unixsocket) {
1200                         unlink(proc->unixsocket->ptr);
1201                     }
1202                 }
1203 
1204                 for (proc = host->unused_procs; proc; proc = proc->next) {
1205                     if (proc->pid > 0) {
1206                         kill(proc->pid, host->kill_signal);
1207                     }
1208                     if (proc->is_local && proc->unixsocket) {
1209                         unlink(proc->unixsocket->ptr);
1210                     }
1211                 }
1212             }
1213         }
1214 
1215         gw_extensions_free(s->exts);
1216         gw_extensions_free(s->exts_auth);
1217         gw_extensions_free(s->exts_resp);
1218     }
1219     free(s);
1220 }
1221 
1222 void gw_free(void *p_d) {
1223     gw_plugin_data * const p = p_d;
1224     if (NULL == p->cvlist) return;
1225     /* (init i to 0 if global context; to 1 to skip empty global context) */
1226     for (int i = !p->cvlist[0].v.u2[1], used = p->nconfig; i < used; ++i) {
1227         config_plugin_value_t *cpv = p->cvlist + p->cvlist[i].v.u2[0];
1228         for (; -1 != cpv->k_id; ++cpv) {
1229             switch (cpv->k_id) {
1230               case 0: /* xxxxx.server */
1231                 if (cpv->vtype == T_CONFIG_LOCAL)
1232                     gw_plugin_config_free(cpv->v.v);
1233                 break;
1234               default:
1235                 break;
1236             }
1237         }
1238     }
1239 }
1240 
1241 void gw_exts_clear_check_local(gw_exts *exts) {
1242     for (uint32_t j = 0; j < exts->used; ++j) {
1243         gw_extension *ex = exts->exts+j;
1244         for (uint32_t n = 0; n < ex->used; ++n) {
1245             ex->hosts[n]->check_local = 0;
1246         }
1247     }
1248 }
1249 
1250 int gw_set_defaults_backend(server *srv, gw_plugin_data *p, const array *a, gw_plugin_config *s, int sh_exec, const char *cpkkey) {
1251     /* per-module plugin_config MUST have common "base class" gw_plugin_config*/
1252     /* per-module plugin_data MUST have pointer-compatible common "base class"
1253      * with gw_plugin_data (stemming from gw_plugin_config compatibility) */
1254 
1255     static const config_plugin_keys_t cpk[] = {
1256       { CONST_STR_LEN("host"),
1257         T_CONFIG_STRING,
1258         T_CONFIG_SCOPE_CONNECTION }
1259      ,{ CONST_STR_LEN("port"),
1260         T_CONFIG_SHORT,
1261         T_CONFIG_SCOPE_CONNECTION }
1262      ,{ CONST_STR_LEN("socket"),
1263         T_CONFIG_STRING,
1264         T_CONFIG_SCOPE_CONNECTION }
1265      ,{ CONST_STR_LEN("listen-backlog"),
1266         T_CONFIG_INT,
1267         T_CONFIG_SCOPE_CONNECTION }
1268      ,{ CONST_STR_LEN("bin-path"),
1269         T_CONFIG_STRING,
1270         T_CONFIG_SCOPE_CONNECTION }
1271      ,{ CONST_STR_LEN("kill-signal"),
1272         T_CONFIG_SHORT,
1273         T_CONFIG_SCOPE_CONNECTION }
1274      ,{ CONST_STR_LEN("check-local"),
1275         T_CONFIG_BOOL,
1276         T_CONFIG_SCOPE_CONNECTION }
1277      ,{ CONST_STR_LEN("mode"),
1278         T_CONFIG_STRING,
1279         T_CONFIG_SCOPE_CONNECTION }
1280      ,{ CONST_STR_LEN("docroot"),
1281         T_CONFIG_STRING,
1282         T_CONFIG_SCOPE_CONNECTION }
1283      ,{ CONST_STR_LEN("min-procs"),
1284         T_CONFIG_SHORT,
1285         T_CONFIG_SCOPE_CONNECTION }
1286      ,{ CONST_STR_LEN("max-procs"),
1287         T_CONFIG_SHORT,
1288         T_CONFIG_SCOPE_CONNECTION }
1289      ,{ CONST_STR_LEN("max-load-per-proc"),
1290         T_CONFIG_SHORT,
1291         T_CONFIG_SCOPE_CONNECTION }
1292      ,{ CONST_STR_LEN("idle-timeout"),
1293         T_CONFIG_SHORT,
1294         T_CONFIG_SCOPE_CONNECTION }
1295      ,{ CONST_STR_LEN("disable-time"),
1296         T_CONFIG_SHORT,
1297         T_CONFIG_SCOPE_CONNECTION }
1298      ,{ CONST_STR_LEN("bin-environment"),
1299         T_CONFIG_ARRAY_KVSTRING,
1300         T_CONFIG_SCOPE_CONNECTION }
1301      ,{ CONST_STR_LEN("bin-copy-environment"),
1302         T_CONFIG_ARRAY_VLIST,
1303         T_CONFIG_SCOPE_CONNECTION }
1304      ,{ CONST_STR_LEN("broken-scriptfilename"),
1305         T_CONFIG_BOOL,
1306         T_CONFIG_SCOPE_CONNECTION }
1307      ,{ CONST_STR_LEN("strip-request-uri"),
1308         T_CONFIG_STRING,
1309         T_CONFIG_SCOPE_CONNECTION }
1310      ,{ CONST_STR_LEN("fix-root-scriptname"),
1311         T_CONFIG_BOOL,
1312         T_CONFIG_SCOPE_CONNECTION }
1313      ,{ CONST_STR_LEN("allow-x-send-file"),
1314         T_CONFIG_BOOL,
1315         T_CONFIG_SCOPE_CONNECTION }
1316      ,{ CONST_STR_LEN("x-sendfile"),
1317         T_CONFIG_BOOL,
1318         T_CONFIG_SCOPE_CONNECTION }
1319      ,{ CONST_STR_LEN("x-sendfile-docroot"),
1320         T_CONFIG_ARRAY_VLIST,
1321         T_CONFIG_SCOPE_CONNECTION }
1322      ,{ CONST_STR_LEN("tcp-fin-propagate"),
1323         T_CONFIG_BOOL,
1324         T_CONFIG_SCOPE_CONNECTION }
1325      ,{ CONST_STR_LEN("connect-timeout"),
1326         T_CONFIG_INT,
1327         T_CONFIG_SCOPE_CONNECTION }
1328      ,{ CONST_STR_LEN("write-timeout"),
1329         T_CONFIG_INT,
1330         T_CONFIG_SCOPE_CONNECTION }
1331      ,{ CONST_STR_LEN("read-timeout"),
1332         T_CONFIG_INT,
1333         T_CONFIG_SCOPE_CONNECTION }
1334      ,{ NULL, 0,
1335         T_CONFIG_UNSET,
1336         T_CONFIG_SCOPE_UNSET }
1337     };
1338 
1339     gw_host *host = NULL;
1340 
1341     int graceful_restart_bg =
1342       config_feature_bool(srv, "server.graceful-restart-bg", 0);
1343 
1344     p->srv_pid = srv->pid;
1345 
1346     s->exts      = gw_extensions_init();
1347     s->exts_auth = gw_extensions_init();
1348     s->exts_resp = gw_extensions_init();
1349     /*s->balance = GW_BALANCE_LEAST_CONNECTION;*//*(default)*/
1350 
1351     /*
1352      * gw.server = ( "<ext>" => ( ... ),
1353      *               "<ext>" => ( ... ) )
1354      */
1355 
1356     for (uint32_t j = 0; j < a->used; ++j) {
1357         data_array *da_ext = (data_array *)a->data[j];
1358 
1359         /*
1360          * da_ext->key == name of the extension
1361          */
1362 
1363         /*
1364          * gw.server = ( "<ext>" =>
1365          *                     ( "<host>" => ( ... ),
1366          *                       "<host>" => ( ... )
1367          *                     ),
1368          *               "<ext>" => ... )
1369          */
1370 
1371         for (uint32_t n = 0; n < da_ext->value.used; ++n) {
1372             data_array * const da_host = (data_array *)da_ext->value.data[n];
1373 
1374             if (da_host->type != TYPE_ARRAY
1375                 || !array_is_kvany(&da_host->value)){
1376                 log_error(srv->errh, __FILE__, __LINE__,
1377                   "unexpected value for gw.server near [%s](string); "
1378                   "expected ( \"ext\" => "
1379                   "( \"backend-label\" => ( \"key\" => \"value\" )))",
1380                   da_host->key.ptr ? da_host->key.ptr : "");
1381                 goto error;
1382             }
1383 
1384             config_plugin_value_t cvlist[sizeof(cpk)/sizeof(cpk[0])+1];
1385             memset(cvlist, 0, sizeof(cvlist));
1386 
1387             array *ca = &da_host->value;
1388             if (!config_plugin_values_init_block(srv, ca, cpk, cpkkey, cvlist))
1389                 goto error;
1390 
1391             unsigned short host_mode = GW_RESPONDER;
1392 
1393             host = gw_host_init();
1394             host->id = &da_host->key;
1395             host->check_local  = 1;
1396             host->min_procs    = 4;
1397             host->max_procs    = 4;
1398             host->max_load_per_proc = 1;
1399             host->idle_timeout = 60;
1400             host->connect_timeout = 8;
1401             host->disable_time = 1;
1402             host->break_scriptfilename_for_php = 0;
1403             host->kill_signal = SIGTERM;
1404             host->fix_root_path_name = 0;
1405             host->listen_backlog = 1024;
1406             host->xsendfile_allow = 0;
1407             host->refcount = 0;
1408 
1409             config_plugin_value_t *cpv = cvlist;
1410             for (; -1 != cpv->k_id; ++cpv) {
1411                 switch (cpv->k_id) {
1412                   case 0: /* host */
1413                     if (!buffer_is_blank(cpv->v.b))
1414                         host->host = cpv->v.b;
1415                     break;
1416                   case 1: /* port */
1417                     host->port = cpv->v.shrt;
1418                     break;
1419                   case 2: /* socket */
1420                     if (!buffer_is_blank(cpv->v.b))
1421                         host->unixsocket = cpv->v.b;
1422                     break;
1423                   case 3: /* listen-backlog */
1424                     host->listen_backlog = cpv->v.u;
1425                     break;
1426                   case 4: /* bin-path */
1427                     if (!buffer_is_blank(cpv->v.b))
1428                         host->bin_path = cpv->v.b;
1429                     break;
1430                   case 5: /* kill-signal */
1431                     host->kill_signal = cpv->v.shrt;
1432                     break;
1433                   case 6: /* check-local */
1434                     host->check_local = (0 != cpv->v.u);
1435                     break;
1436                   case 7: /* mode */
1437                     if (!buffer_is_blank(cpv->v.b)) {
1438                         const buffer *b = cpv->v.b;
1439                         if (buffer_eq_slen(b, CONST_STR_LEN("responder")))
1440                             host_mode = GW_RESPONDER;
1441                         else if (buffer_eq_slen(b, CONST_STR_LEN("authorizer")))
1442                             host_mode = GW_AUTHORIZER;
1443                         else
1444                             log_error(srv->errh, __FILE__, __LINE__,
1445                               "WARNING: unknown gw mode: %s "
1446                               "(ignored, mode set to responder)", b->ptr);
1447                     }
1448                     break;
1449                   case 8: /* docroot */
1450                     if (!buffer_is_blank(cpv->v.b))
1451                         host->docroot = cpv->v.b;
1452                     break;
1453                   case 9: /* min-procs */
1454                     host->min_procs = cpv->v.shrt;
1455                     break;
1456                   case 10:/* max-procs */
1457                     host->max_procs = cpv->v.shrt;
1458                     break;
1459                   case 11:/* max-load-per-proc */
1460                     host->max_load_per_proc = cpv->v.shrt;
1461                     break;
1462                   case 12:/* idle-timeout */
1463                     host->idle_timeout = cpv->v.shrt;
1464                     break;
1465                   case 13:/* disable-time */
1466                     host->disable_time = cpv->v.shrt;
1467                     break;
1468                   case 14:/* bin-environment */
1469                     host->bin_env = cpv->v.a;
1470                     break;
1471                   case 15:/* bin-copy-environment */
1472                     host->bin_env_copy = cpv->v.a;
1473                     break;
1474                   case 16:/* broken-scriptfilename */
1475                     host->break_scriptfilename_for_php = (0 != cpv->v.u);
1476                     break;
1477                   case 17:/* strip-request-uri */
1478                     host->strip_request_uri = cpv->v.b;
1479                     if (buffer_has_slash_suffix(host->strip_request_uri)) {
1480                         buffer *b; /*(remove trailing slash; see http_cgi.c)*/
1481                         *(const buffer **)&b = host->strip_request_uri;
1482                         buffer_truncate(b, buffer_clen(b)-1);
1483                     }
1484                     break;
1485                   case 18:/* fix-root-scriptname */
1486                     host->fix_root_path_name = (0 != cpv->v.u);
1487                     break;
1488                   case 19:/* allow-x-send-file */
1489                     host->xsendfile_allow = (0 != cpv->v.u);
1490                     break;
1491                   case 20:/* x-sendfile */
1492                     host->xsendfile_allow = (0 != cpv->v.u);
1493                     break;
1494                   case 21:/* x-sendfile-docroot */
1495                     host->xsendfile_docroot = cpv->v.a;
1496                     if (cpv->v.a->used) {
1497                         for (uint32_t k = 0; k < cpv->v.a->used; ++k) {
1498                             data_string *ds = (data_string *)cpv->v.a->data[k];
1499                             if (ds->type != TYPE_STRING) {
1500                                 log_error(srv->errh, __FILE__, __LINE__,
1501                                   "unexpected type for x-sendfile-docroot; "
1502                                   "expected: \"x-sendfile-docroot\" => "
1503                                   "( \"/allowed/path\", ... )");
1504                                 goto error;
1505                             }
1506                             if (ds->value.ptr[0] != '/') {
1507                                 log_error(srv->errh, __FILE__, __LINE__,
1508                                   "x-sendfile-docroot paths must begin with "
1509                                   "'/'; invalid: \"%s\"", ds->value.ptr);
1510                                 goto error;
1511                             }
1512                             buffer_path_simplify(&ds->value);
1513                             buffer_append_slash(&ds->value);
1514                         }
1515                     }
1516                     break;
1517                   case 22:/* tcp-fin-propagate */
1518                     host->tcp_fin_propagate = (0 != cpv->v.u);
1519                     break;
1520                   case 23:/* connect-timeout */
1521                     host->connect_timeout = cpv->v.u;
1522                     break;
1523                   case 24:/* write-timeout */
1524                     host->write_timeout = cpv->v.u;
1525                     break;
1526                   case 25:/* read-timeout */
1527                     host->read_timeout = cpv->v.u;
1528                     break;
1529                   default:
1530                     break;
1531                 }
1532             }
1533 
1534             for (uint32_t m = 0; m < da_host->value.used; ++m) {
1535                 if (NULL != strchr(da_host->value.data[m]->key.ptr, '_')) {
1536                     log_error(srv->errh, __FILE__, __LINE__,
1537                       "incorrect directive contains underscore ('_') instead of dash ('-'): %s",
1538                       da_host->value.data[m]->key.ptr);
1539                 }
1540             }
1541 
1542             if ((host->host || host->port) && host->unixsocket) {
1543                 log_error(srv->errh, __FILE__, __LINE__,
1544                   "either host/port or socket have to be set in: "
1545                   "%s = (%s => (%s ( ...", cpkkey, da_ext->key.ptr,
1546                   da_host->key.ptr);
1547 
1548                 goto error;
1549             }
1550 
1551             if (host->host && *host->host->ptr == '/' && !host->unixsocket) {
1552                 host->unixsocket = host->host;
1553             }
1554 
1555             if (host->unixsocket) {
1556                 /* unix domain socket */
1557                 struct sockaddr_un un;
1558 
1559                 if (buffer_clen(host->unixsocket) + 1 > sizeof(un.sun_path) - 2) {
1560                     log_error(srv->errh, __FILE__, __LINE__,
1561                       "unixsocket is too long in: %s = (%s => (%s ( ...",
1562                       cpkkey, da_ext->key.ptr, da_host->key.ptr);
1563 
1564                     goto error;
1565                 }
1566 
1567                 if (host->bin_path) {
1568                     gw_host *duplicate = unixsocket_is_dup(p, host->unixsocket);
1569                     if (NULL != duplicate) {
1570                         if (!buffer_is_equal(host->bin_path, duplicate->bin_path)) {
1571                             log_error(srv->errh, __FILE__, __LINE__,
1572                               "duplicate unixsocket path: %s",
1573                               host->unixsocket->ptr);
1574                             goto error;
1575                         }
1576                         gw_host_free(host);
1577                         host = duplicate;
1578                         ++host->refcount;
1579                     }
1580                 }
1581 
1582                 host->family = AF_UNIX;
1583             } else {
1584                 /* tcp/ip */
1585 
1586                 if (!host->host && !host->bin_path) {
1587                     log_error(srv->errh, __FILE__, __LINE__,
1588                       "host or bin-path have to be set in: "
1589                       "%s = (%s => (%s ( ...", cpkkey, da_ext->key.ptr,
1590                       da_host->key.ptr);
1591 
1592                     goto error;
1593                 } else if (0 == host->port) {
1594                     host->port = 80;
1595                 }
1596 
1597                 if (!host->host) {
1598                     static const buffer lhost ={CONST_STR_LEN("127.0.0.1")+1,0};
1599                     host->host = &lhost;
1600                 }
1601 
1602                 host->family = (NULL != strchr(host->host->ptr, ':'))
1603                   ? AF_INET6
1604                   : AF_INET;
1605             }
1606             if (!host->refcount)
1607                 gw_status_init_host(host);
1608 
1609             if (host->refcount) {
1610                 /* already init'd; skip spawning */
1611             } else if (host->bin_path) {
1612                 /* a local socket + self spawning */
1613                 struct stat st;
1614                 parse_binpath(&host->args, host->bin_path);
1615                 if (0 != stat(host->args.ptr[0], &st) || !S_ISREG(st.st_mode)
1616                     || !(st.st_mode & (S_IXUSR | S_IXGRP | S_IXOTH))) {
1617                     log_error(srv->errh, __FILE__, __LINE__,
1618                       "invalid \"bin-path\" => \"%s\" "
1619                       "(check that file exists, is regular file, "
1620                       "and is executable by lighttpd)", host->bin_path->ptr);
1621                 }
1622 
1623                 if (sh_exec) {
1624                     /*(preserve prior behavior for SCGI exec of command)*/
1625                     /*(admin should really prefer to put
1626                      * any complex command into a script)*/
1627                     for (uint32_t m = 0; m < host->args.used; ++m)
1628                         free(host->args.ptr[m]);
1629                     free(host->args.ptr);
1630 
1631                     host->args.ptr = calloc(4, sizeof(char *));
1632                     force_assert(host->args.ptr);
1633                     host->args.used = 3;
1634                     host->args.size = 4;
1635                     host->args.ptr[0] = malloc(sizeof("/bin/sh"));
1636                     force_assert(host->args.ptr[0]);
1637                     memcpy(host->args.ptr[0], "/bin/sh", sizeof("/bin/sh"));
1638                     host->args.ptr[1] = malloc(sizeof("-c"));
1639                     force_assert(host->args.ptr[1]);
1640                     memcpy(host->args.ptr[1], "-c", sizeof("-c"));
1641                     host->args.ptr[2] =
1642                       malloc(sizeof("exec ")-1+buffer_clen(host->bin_path)+1);
1643                     force_assert(host->args.ptr[2]);
1644                     memcpy(host->args.ptr[2], "exec ", sizeof("exec ")-1);
1645                     memcpy(host->args.ptr[2]+sizeof("exec ")-1,
1646                            host->bin_path->ptr, buffer_clen(host->bin_path)+1);
1647                     host->args.ptr[3] = NULL;
1648                 }
1649 
1650                 if (host->min_procs > host->max_procs)
1651                     host->min_procs = host->max_procs;
1652                 if (host->min_procs!= host->max_procs
1653                     && 0 != srv->srvconf.max_worker) {
1654                     host->min_procs = host->max_procs;
1655                     log_error(srv->errh, __FILE__, __LINE__,
1656                       "adaptive backend spawning disabled "
1657                       "(server.max_worker is non-zero)");
1658                 }
1659                 if (host->max_load_per_proc < 1)
1660                     host->max_load_per_proc = 0;
1661 
1662                 if (s->debug) {
1663                     log_error(srv->errh, __FILE__, __LINE__,
1664                       "--- gw spawning local"
1665                       "\n\tproc: %s"
1666                       "\n\tport: %hu"
1667                       "\n\tsocket %s"
1668                       "\n\tmin-procs: %d"
1669                       "\n\tmax-procs: %d",
1670                       host->bin_path->ptr,
1671                       host->port,
1672                       host->unixsocket ? host->unixsocket->ptr : "",
1673                       host->min_procs,
1674                       host->max_procs);
1675                 }
1676 
1677                 for (uint32_t pno = 0; pno < host->min_procs; ++pno) {
1678                     gw_proc * const proc = gw_proc_init(host);
1679 
1680                     if (s->debug) {
1681                         log_error(srv->errh, __FILE__, __LINE__,
1682                           "--- gw spawning"
1683                           "\n\tport: %hu"
1684                           "\n\tsocket %s"
1685                           "\n\tcurrent: %u / %u",
1686                           host->port,
1687                           host->unixsocket ? host->unixsocket->ptr : "",
1688                           pno, host->max_procs);
1689                     }
1690 
1691                     if (0 != gw_proc_sockaddr_init(host, proc, srv->errh)) {
1692                         gw_proc_free(proc);
1693                         goto error;
1694                     }
1695 
1696                     if (!srv->srvconf.preflight_check
1697                         && gw_spawn_connection(host, proc, srv->errh, s->debug)) {
1698                         log_error(srv->errh, __FILE__, __LINE__,
1699                           "[ERROR]: spawning gw failed.");
1700                         gw_proc_free(proc);
1701                         goto error;
1702                     }
1703 
1704                     proc->next = host->first;
1705                     if (host->first) host->first->prev = proc;
1706                     host->first = proc;
1707                     ++host->num_procs;
1708                 }
1709 
1710                 if (graceful_restart_bg) {
1711                     /*(set flag to false to avoid repeating)*/
1712                     graceful_restart_bg = 0;
1713                     log_error(srv->errh, __FILE__, __LINE__,
1714                       "server.graceful-restart-bg disabled "
1715                       "(incompatible with %s.server \"bin-path\")",
1716                       p->self->name);
1717                     data_unset * const du =
1718                       array_get_data_unset(srv->srvconf.feature_flags,
1719                         CONST_STR_LEN("server.graceful-restart-bg"));
1720                     if (du->type == TYPE_STRING)
1721                         buffer_copy_string_len(&((data_string *)du)->value,
1722                                                CONST_STR_LEN("false"));
1723                     else /* (du->type == TYPE_INTEGER) */
1724                         ((data_integer *)du)->value = 0;
1725                 }
1726             } else {
1727                 gw_proc * const proc = gw_proc_init(host);
1728                 host->first = proc;
1729                 ++host->num_procs;
1730                 host->min_procs = 1;
1731                 host->max_procs = 1;
1732                 if (0 != gw_proc_sockaddr_init(host, proc, srv->errh)) goto error;
1733                 gw_proc_set_state(host, proc, PROC_STATE_RUNNING);
1734             }
1735 
1736             const buffer * const h = host->host ? host->host : host->unixsocket;
1737             host->gw_hash = gw_hash(BUF_PTR_LEN(h), DJBHASH_INIT);
1738 
1739             /* s->exts is list of exts -> hosts
1740              * s->exts now used as combined list
1741              *   of authorizer and responder hosts (for backend maintenance)
1742              * s->exts_auth is list of exts -> authorizer hosts
1743              * s->exts_resp is list of exts -> responder hosts
1744              * For each path/extension:
1745              * there may be an independent GW_AUTHORIZER and GW_RESPONDER
1746              * (The GW_AUTHORIZER and GW_RESPONDER could be handled by the same
1747              *  host, and an admin might want to do that for large uploads,
1748              *  since GW_AUTHORIZER runs prior to receiving (potentially large)
1749              *  request body from client and can authorizer or deny request
1750              *  prior to receiving the full upload)
1751              */
1752             gw_extension_insert(s->exts, &da_ext->key, host);
1753 
1754             if (host_mode == GW_AUTHORIZER) {
1755                 ++host->refcount;
1756                 gw_extension_insert(s->exts_auth, &da_ext->key, host);
1757             } else if (host_mode == GW_RESPONDER) {
1758                 ++host->refcount;
1759                 gw_extension_insert(s->exts_resp, &da_ext->key, host);
1760             } /*(else should have been rejected above)*/
1761 
1762             host = NULL;
1763         }
1764     }
1765 
1766     return 1;
1767 
1768 error:
1769     if (NULL != host) gw_host_free(host);
1770     return 0;
1771 }
1772 
1773 int gw_get_defaults_balance(server *srv, const buffer *b) {
1774     if (!b || buffer_is_blank(b))
1775         return GW_BALANCE_LEAST_CONNECTION;
1776     if (buffer_eq_slen(b, CONST_STR_LEN("fair")))
1777         return GW_BALANCE_LEAST_CONNECTION;
1778     if (buffer_eq_slen(b, CONST_STR_LEN("least-connection")))
1779         return GW_BALANCE_LEAST_CONNECTION;
1780     if (buffer_eq_slen(b, CONST_STR_LEN("round-robin")))
1781         return GW_BALANCE_RR;
1782     if (buffer_eq_slen(b, CONST_STR_LEN("hash")))
1783         return GW_BALANCE_HASH;
1784     if (buffer_eq_slen(b, CONST_STR_LEN("sticky")))
1785         return GW_BALANCE_STICKY;
1786 
1787     log_error(srv->errh, __FILE__, __LINE__,
1788       "xxxxx.balance has to be one of: "
1789       "least-connection, round-robin, hash, sticky, but not: %s", b->ptr);
1790     return GW_BALANCE_LEAST_CONNECTION;
1791 }
1792 
1793 
1794 static void gw_set_state(gw_handler_ctx *hctx, gw_connection_state_t state) {
1795     hctx->state = state;
1796     /*hctx->state_timestamp = log_monotonic_secs;*/
1797 }
1798 
1799 
1800 void gw_set_transparent(gw_handler_ctx *hctx) {
1801     if (AF_UNIX != hctx->host->family) {
1802         if (-1 == fdevent_set_tcp_nodelay(hctx->fd, 1)) {
1803             /*(error, but not critical)*/
1804         }
1805     }
1806     hctx->wb_reqlen = -1;
1807     gw_set_state(hctx, GW_STATE_WRITE);
1808 }
1809 
1810 
1811 static void gw_host_hctx_enq(gw_handler_ctx * const hctx) {
1812     gw_host * const host = hctx->host;
1813     /*if (__builtin_expect( (host == NULL), 0)) return;*/
1814 
1815     hctx->prev = NULL;
1816     hctx->next = host->hctxs;
1817     if (hctx->next)
1818         hctx->next->prev = hctx;
1819     host->hctxs = hctx;
1820 }
1821 
1822 
1823 static void gw_host_hctx_deq(gw_handler_ctx * const hctx) {
1824     /*if (__builtin_expect( (hctx->host == NULL), 0)) return;*/
1825 
1826     if (hctx->prev)
1827         hctx->prev->next = hctx->next;
1828     else
1829         hctx->host->hctxs= hctx->next;
1830 
1831     if (hctx->next)
1832         hctx->next->prev = hctx->prev;
1833 
1834     hctx->next = NULL;
1835     hctx->prev = NULL;
1836 }
1837 
1838 
1839 static void gw_backend_close(gw_handler_ctx * const hctx, request_st * const r) {
1840     if (hctx->fd >= 0) {
1841         fdevent_fdnode_event_del(hctx->ev, hctx->fdn);
1842         /*fdevent_unregister(ev, hctx->fd);*//*(handled below)*/
1843         fdevent_sched_close(hctx->ev, hctx->fd, 1);
1844         hctx->fdn = NULL;
1845         hctx->fd = -1;
1846         gw_host_hctx_deq(hctx);
1847     }
1848 
1849     if (hctx->host) {
1850         if (hctx->proc) {
1851             gw_proc_release(hctx->host, hctx->proc, hctx->conf.debug,
1852                             r->conf.errh);
1853             hctx->proc = NULL;
1854         }
1855 
1856         gw_host_reset(hctx->host);
1857         hctx->host = NULL;
1858     }
1859 }
1860 
1861 static void gw_connection_close(gw_handler_ctx * const hctx, request_st * const r) {
1862     gw_plugin_data *p = hctx->plugin_data;
1863 
1864     gw_backend_close(hctx, r);
1865     handler_ctx_free(hctx);
1866     r->plugin_ctx[p->id] = NULL;
1867 
1868     if (r->handler_module == p->self) {
1869         http_response_backend_done(r);
1870     }
1871 }
1872 
1873 static handler_t gw_reconnect(gw_handler_ctx * const hctx, request_st * const r) {
1874     gw_backend_close(hctx, r);
1875 
1876     hctx->host = gw_host_get(r,hctx->ext,hctx->conf.balance,hctx->conf.debug);
1877     if (NULL == hctx->host) return HANDLER_FINISHED;
1878 
1879     gw_host_assign(hctx->host);
1880     hctx->request_id = 0;
1881     hctx->opts.xsendfile_allow = hctx->host->xsendfile_allow;
1882     hctx->opts.xsendfile_docroot = hctx->host->xsendfile_docroot;
1883     gw_set_state(hctx, GW_STATE_INIT);
1884     return HANDLER_COMEBACK;
1885 }
1886 
1887 
1888 handler_t gw_handle_request_reset(request_st * const r, void *p_d) {
1889     gw_plugin_data *p = p_d;
1890     gw_handler_ctx *hctx = r->plugin_ctx[p->id];
1891     if (hctx) gw_connection_close(hctx, r);
1892 
1893     return HANDLER_GO_ON;
1894 }
1895 
1896 
1897 __attribute_cold__
1898 static void gw_conditional_tcp_fin(gw_handler_ctx * const hctx, request_st * const r) {
1899     /*assert(r->conf.stream_request_body & FDEVENT_STREAM_REQUEST_TCP_FIN);*/
1900     if (!chunkqueue_is_empty(&hctx->wb))return;
1901     if (!hctx->host->tcp_fin_propagate) return;
1902     if (hctx->gw_mode == GW_AUTHORIZER) return;
1903     if (r->conf.stream_request_body & FDEVENT_STREAM_REQUEST_BACKEND_SHUT_WR)
1904         return;
1905 
1906     /* propagate shutdown SHUT_WR to backend if TCP half-close on con->fd */
1907     r->conf.stream_request_body |= FDEVENT_STREAM_REQUEST_BACKEND_SHUT_WR;
1908     r->conf.stream_request_body &= ~FDEVENT_STREAM_REQUEST_POLLIN;
1909     r->con->is_readable = 0;
1910     shutdown(hctx->fd, SHUT_WR);
1911     fdevent_fdnode_event_clr(hctx->ev, hctx->fdn, FDEVENT_OUT);
1912 }
1913 
1914 static handler_t gw_write_refill_wb(gw_handler_ctx * const hctx, request_st * const r) {
1915     if (chunkqueue_is_empty(&r->reqbody_queue))
1916         return HANDLER_GO_ON;
1917     if (hctx->stdin_append) {
1918         if (chunkqueue_length(&hctx->wb) < 65536 - 16384)
1919             return hctx->stdin_append(hctx);
1920     }
1921     else {
1922         const chunk * const c = r->reqbody_queue.last;
1923         const off_t qlen = chunkqueue_length(&r->reqbody_queue);
1924         if (c->type == FILE_CHUNK) {
1925             /*(move all but last chunk if reqbody_queue using tempfiles, unless
1926              * hctx->wb is empty and only one chunk, then move last chunk)*/
1927             if (c != r->reqbody_queue.first)
1928                 chunkqueue_steal(&hctx->wb, &r->reqbody_queue,
1929                                  qlen - (c->file.length-c->offset));
1930             else if (chunkqueue_is_empty(&hctx->wb))
1931                 chunkqueue_append_chunkqueue(&hctx->wb, &r->reqbody_queue);
1932         }
1933         else if (qlen + chunkqueue_length(&hctx->wb) > 65536) {
1934             if (0 != chunkqueue_steal_with_tempfiles(&hctx->wb,
1935                        &r->reqbody_queue, qlen, r->conf.errh))
1936                 return HANDLER_ERROR;
1937         }
1938         else
1939             chunkqueue_append_chunkqueue(&hctx->wb, &r->reqbody_queue);
1940     }
1941     return HANDLER_GO_ON;
1942 }
1943 
1944 static handler_t gw_write_request(gw_handler_ctx * const hctx, request_st * const r) {
1945     switch(hctx->state) {
1946     case GW_STATE_INIT:
1947         /* do we have a running process for this host (max-procs) ? */
1948         hctx->proc = NULL;
1949 
1950         for (gw_proc *proc = hctx->host->first; proc; proc = proc->next) {
1951              if (proc->state == PROC_STATE_RUNNING) {
1952                  hctx->proc = proc;
1953                  break;
1954              }
1955         }
1956 
1957         /* all children are dead */
1958         if (hctx->proc == NULL) {
1959             return HANDLER_ERROR;
1960         }
1961 
1962         /* check the other procs if they have a lower load */
1963         for (gw_proc *proc = hctx->proc->next; proc; proc = proc->next) {
1964             if (proc->state != PROC_STATE_RUNNING) continue;
1965             if (proc->load < hctx->proc->load) hctx->proc = proc;
1966         }
1967 
1968         gw_proc_load_inc(hctx->host, hctx->proc);
1969 
1970         hctx->fd = fdevent_socket_nb_cloexec(hctx->host->family,SOCK_STREAM,0);
1971         if (-1 == hctx->fd) {
1972             log_perror(r->conf.errh, __FILE__, __LINE__,
1973               "socket() failed (cur_fds:%d) (max_fds:%d)",
1974               r->con->srv->cur_fds, r->con->srv->max_fds);
1975             return HANDLER_ERROR;
1976         }
1977 
1978         ++r->con->srv->cur_fds;
1979 
1980         hctx->fdn = fdevent_register(hctx->ev,hctx->fd,gw_handle_fdevent,hctx);
1981 
1982         if (hctx->proc->is_local) {
1983             hctx->pid = hctx->proc->pid;
1984         }
1985 
1986         hctx->write_ts = log_monotonic_secs;
1987         gw_host_hctx_enq(hctx);
1988         switch (gw_establish_connection(r, hctx->host, hctx->proc, hctx->pid,
1989                                         hctx->fd, hctx->conf.debug)) {
1990         case 1: /* connection is in progress */
1991             fdevent_fdnode_event_set(hctx->ev, hctx->fdn, FDEVENT_OUT);
1992             gw_set_state(hctx, GW_STATE_CONNECT_DELAYED);
1993             return HANDLER_WAIT_FOR_EVENT;
1994         case -1:/* connection error */
1995             return HANDLER_ERROR;
1996         case 0: /* everything is ok, go on */
1997             hctx->reconnects = 0;
1998             break;
1999         }
2000         __attribute_fallthrough__
2001     case GW_STATE_CONNECT_DELAYED:
2002         if (hctx->state == GW_STATE_CONNECT_DELAYED) { /*(not GW_STATE_INIT)*/
2003             if (!(fdevent_fdnode_interest(hctx->fdn) & FDEVENT_OUT))
2004                 return HANDLER_WAIT_FOR_EVENT;
2005             int socket_error = fdevent_connect_status(hctx->fd);
2006             if (socket_error != 0) {
2007                 gw_proc_connect_error(r, hctx->host, hctx->proc, hctx->pid,
2008                                       socket_error, hctx->conf.debug);
2009                 return HANDLER_ERROR;
2010             }
2011             /* go on with preparing the request */
2012             hctx->write_ts = log_monotonic_secs;
2013         }
2014 
2015         gw_proc_connect_success(hctx->host, hctx->proc, hctx->conf.debug, r);
2016 
2017         gw_set_state(hctx, GW_STATE_PREPARE_WRITE);
2018         __attribute_fallthrough__
2019     case GW_STATE_PREPARE_WRITE:
2020         /* ok, we have the connection */
2021 
2022         {
2023             handler_t rc = hctx->create_env(hctx);
2024             if (HANDLER_GO_ON != rc) {
2025                 if (HANDLER_FINISHED != rc && HANDLER_ERROR != rc)
2026                     fdevent_fdnode_event_clr(hctx->ev, hctx->fdn, FDEVENT_OUT);
2027                 return rc;
2028             }
2029         }
2030 
2031         /*(disable Nagle algorithm if streaming and content-length unknown)*/
2032         if (AF_UNIX != hctx->host->family) {
2033             if (r->reqbody_length < 0) {
2034                 if (-1 == fdevent_set_tcp_nodelay(hctx->fd, 1)) {
2035                     /*(error, but not critical)*/
2036                 }
2037             }
2038         }
2039 
2040         hctx->read_ts = log_monotonic_secs;
2041         fdevent_fdnode_event_add(hctx->ev, hctx->fdn, FDEVENT_IN|FDEVENT_RDHUP);
2042         gw_set_state(hctx, GW_STATE_WRITE);
2043         __attribute_fallthrough__
2044     case GW_STATE_WRITE:
2045         if (!chunkqueue_is_empty(&hctx->wb)) {
2046             log_error_st * const errh = r->conf.errh;
2047           #if 0
2048             if (hctx->conf.debug > 1) {
2049                 log_error(errh, __FILE__, __LINE__, "sdsx",
2050                   "send data to backend (fd=%d), size=%zu",
2051                   hctx->fd, chunkqueue_length(&hctx->wb));
2052             }
2053           #endif
2054             off_t bytes_out = hctx->wb.bytes_out;
2055             if (r->con->srv->network_backend_write(hctx->fd, &hctx->wb,
2056                                                    MAX_WRITE_LIMIT, errh) < 0) {
2057                 switch(errno) {
2058                 case EPIPE:
2059                 case ENOTCONN:
2060                 case ECONNRESET:
2061                     /* the connection got dropped after accept()
2062                      * we don't care about that --
2063                      * if you accept() it, you have to handle it.
2064                      */
2065                     log_error(errh, __FILE__, __LINE__,
2066                       "connection was dropped after accept() "
2067                       "(perhaps the gw process died), "
2068                       "write-offset: %lld socket: %s",
2069                       (long long)hctx->wb.bytes_out,
2070                       hctx->proc->connection_name->ptr);
2071                     return HANDLER_ERROR;
2072                 default:
2073                     log_perror(errh, __FILE__, __LINE__, "write failed");
2074                     return HANDLER_ERROR;
2075                 }
2076             }
2077             else if (hctx->wb.bytes_out > bytes_out) {
2078                 hctx->write_ts = hctx->proc->last_used = log_monotonic_secs;
2079                 handler_t rc = gw_write_refill_wb(hctx, r);
2080                 if (HANDLER_GO_ON != rc) return rc;
2081             }
2082         }
2083 
2084         if (hctx->wb.bytes_out == hctx->wb_reqlen) {
2085             fdevent_fdnode_event_clr(hctx->ev, hctx->fdn, FDEVENT_OUT);
2086             gw_set_state(hctx, GW_STATE_READ);
2087         } else {
2088             off_t wblen = chunkqueue_length(&hctx->wb);
2089             if ((hctx->wb.bytes_in < hctx->wb_reqlen || hctx->wb_reqlen < 0)
2090                 && wblen < 65536 - 16384) {
2091                 /*(r->conf.stream_request_body & FDEVENT_STREAM_REQUEST)*/
2092                 if (!(r->conf.stream_request_body
2093                       & FDEVENT_STREAM_REQUEST_POLLIN)) {
2094                     r->conf.stream_request_body |=
2095                         FDEVENT_STREAM_REQUEST_POLLIN;
2096                     r->con->is_readable = 1; /* trigger optimistic client read */
2097                 }
2098             }
2099             if (0 == wblen) {
2100                 fdevent_fdnode_event_clr(hctx->ev, hctx->fdn, FDEVENT_OUT);
2101             }
2102             else if (!(fdevent_fdnode_interest(hctx->fdn) & FDEVENT_OUT)) {
2103                 hctx->write_ts = log_monotonic_secs;
2104                 fdevent_fdnode_event_add(hctx->ev, hctx->fdn, FDEVENT_OUT);
2105             }
2106         }
2107 
2108         if (r->conf.stream_request_body
2109             & FDEVENT_STREAM_REQUEST_TCP_FIN)
2110             gw_conditional_tcp_fin(hctx, r);
2111 
2112         return HANDLER_WAIT_FOR_EVENT;
2113     case GW_STATE_READ:
2114         /* waiting for a response */
2115         return HANDLER_WAIT_FOR_EVENT;
2116     default:
2117         log_error(r->conf.errh, __FILE__, __LINE__,
2118           "(debug) unknown state");
2119         return HANDLER_ERROR;
2120     }
2121 }
2122 
2123 
2124 __attribute_cold__
2125 __attribute_noinline__
2126 static handler_t gw_backend_error(gw_handler_ctx * const hctx, request_st * const r)
2127 {
2128     if (hctx->backend_error) hctx->backend_error(hctx);
2129     http_response_backend_error(r);
2130     gw_connection_close(hctx, r);
2131     return HANDLER_FINISHED;
2132 }
2133 
2134 
2135 static handler_t gw_recv_response(gw_handler_ctx *hctx, request_st *r);
2136 
2137 
2138 __attribute_cold__
2139 static handler_t gw_write_error(gw_handler_ctx * const hctx, request_st * const r) {
2140 
2141     if (hctx->state == GW_STATE_INIT ||
2142         hctx->state == GW_STATE_CONNECT_DELAYED) {
2143 
2144         /* (optimization to detect backend process exit while processing a
2145          *  large number of ready events; (this block could be removed)) */
2146         if (hctx->proc && hctx->proc->is_local) {
2147             server * const srv = r->con->srv;
2148             if (0 == srv->srvconf.max_worker)
2149                 gw_restart_dead_procs(hctx->host,srv->errh,hctx->conf.debug,0);
2150         }
2151 
2152         /* cleanup this request and let request handler start request again */
2153         if (hctx->reconnects++ < 5) return gw_reconnect(hctx, r);
2154     }
2155     else {
2156         /* backend might not read request body (even though backend should)
2157          * before sending response, so it is possible to get EPIPE trying to
2158          * write request body to the backend when backend has already sent a
2159          * response.  If called from gw_handle_fdevent(), response should have
2160          * been read prior to getting here.  However, if reqbody arrived on
2161          * client side, and called gw_handle_subrequest() and we tried to write
2162          * in gw_send_request() in state GW_STATE_WRITE, then it is possible to
2163          * get EPIPE and error out here when response is waiting to be read from
2164          * kernel socket buffers.  Since we did not actually receive FDEVENT_HUP
2165          * or FDEVENT_RDHUP, calling gw_handle_fdevent() and fabricating
2166          * FDEVENT_RDHUP would cause an infinite loop trying to read().
2167          * Instead, try once to read (small) response in this theoretical race*/
2168         handler_t rc = gw_recv_response(hctx, r);   /*(might invalidate hctx)*/
2169         if (rc != HANDLER_GO_ON) return rc;         /*(unless HANDLER_GO_ON)*/
2170     }
2171 
2172     /*(r->status == 400 if hctx->create_env() failed)*/
2173     if (!r->resp_body_started && r->http_status < 500 && r->http_status != 400)
2174         r->http_status = 503; /* Service Unavailable */
2175 
2176     return gw_backend_error(hctx, r); /* HANDLER_FINISHED */
2177 }
2178 
2179 static handler_t gw_send_request(gw_handler_ctx * const hctx, request_st * const r) {
2180     handler_t rc = gw_write_request(hctx, r);
2181     return (HANDLER_ERROR != rc) ? rc : gw_write_error(hctx, r);
2182 }
2183 
2184 
2185 handler_t gw_handle_subrequest(request_st * const r, void *p_d) {
2186     gw_plugin_data *p = p_d;
2187     gw_handler_ctx *hctx = r->plugin_ctx[p->id];
2188     if (NULL == hctx) return HANDLER_GO_ON;
2189 
2190     const int revents = hctx->revents;
2191     if (revents) {
2192         hctx->revents = 0;
2193         handler_t rc = gw_process_fdevent(hctx, r, revents);
2194         if (rc != HANDLER_GO_ON && rc != HANDLER_WAIT_FOR_EVENT)
2195             return rc;             /*(might invalidate hctx)*/
2196     }
2197 
2198     if ((r->conf.stream_response_body & FDEVENT_STREAM_RESPONSE_BUFMIN)
2199         && r->resp_body_started) {
2200         if (chunkqueue_length(&r->write_queue) > 65536 - 4096) {
2201             /* Note: if apps inheriting gw_handle use hctx->rb, then those apps
2202              * are responsible for limiting amount of data buffered in memory
2203              * in hctx->rb.  Currently, mod_fastcgi is the only core app doing
2204              * so, and the maximum FCGI_Record size is 8 + 65535 + 255 = 65798
2205              * (FCGI_HEADER_LEN(8)+contentLength(65535)+paddingLength(255)) */
2206             fdevent_fdnode_event_clr(hctx->ev, hctx->fdn, FDEVENT_IN);
2207         }
2208         else if (!(fdevent_fdnode_interest(hctx->fdn) & FDEVENT_IN)) {
2209             /* optimistic read from backend */
2210             handler_t rc;
2211             rc = gw_recv_response(hctx, r);          /*(might invalidate hctx)*/
2212             if (rc != HANDLER_GO_ON) return rc;      /*(unless HANDLER_GO_ON)*/
2213             hctx->read_ts = log_monotonic_secs;
2214             fdevent_fdnode_event_add(hctx->ev, hctx->fdn, FDEVENT_IN);
2215         }
2216     }
2217 
2218     /* (do not receive request body before GW_AUTHORIZER has run or else
2219      *  the request body is discarded with handler_ctx_clear() after running
2220      *  the FastCGI Authorizer) */
2221 
2222     if (hctx->gw_mode != GW_AUTHORIZER
2223         && (0 == hctx->wb.bytes_in
2224             ? (r->state == CON_STATE_READ_POST || -1 == hctx->wb_reqlen)
2225             : (hctx->wb.bytes_in < hctx->wb_reqlen || hctx->wb_reqlen < 0))) {
2226         /* leave excess data in r->reqbody_queue, which is
2227          * buffered to disk if too large and backend can not keep up */
2228         /*(64k - 4k to attempt to avoid temporary files
2229          * in conjunction with FDEVENT_STREAM_REQUEST_BUFMIN)*/
2230         if (chunkqueue_length(&hctx->wb) > 65536 - 4096
2231             && (r->conf.stream_request_body & FDEVENT_STREAM_REQUEST_BUFMIN)) {
2232             r->conf.stream_request_body &= ~FDEVENT_STREAM_REQUEST_POLLIN;
2233             return HANDLER_WAIT_FOR_EVENT;
2234         }
2235         else {
2236             handler_t rc = r->con->reqbody_read(r);
2237 
2238             if (hctx->opts.backend == BACKEND_PROXY) {
2239                 if (hctx->state == GW_STATE_INIT /* ??? < GW_STATE_WRITE ??? */
2240                     && rc == HANDLER_WAIT_FOR_EVENT
2241                     /* streaming flags might not be set yet
2242                      * if hctx->create_env() not called yet */
2243                     && ((r->conf.stream_request_body & FDEVENT_STREAM_REQUEST)
2244                         || r->h2_connect_ext))
2245                     rc = HANDLER_GO_ON;
2246                     /* connect() to backend proxy w/o waiting for any request body*/
2247             }
2248             else if (-1 == r->reqbody_length) {
2249                 /* XXX: create configurable flag */
2250                 /* CGI environment requires that Content-Length be set.
2251                  * Send 411 Length Required if Content-Length missing.
2252                  * (occurs here if client sends Transfer-Encoding: chunked
2253                  *  and module is flagged to stream request body to backend) */
2254                 return (r->conf.stream_request_body & FDEVENT_STREAM_REQUEST)
2255                   ? http_response_reqbody_read_error(r, 411)
2256                   : (rc == HANDLER_GO_ON) ? HANDLER_WAIT_FOR_EVENT : rc;
2257             }
2258 
2259             if (hctx->wb_reqlen < -1 && r->reqbody_length >= 0) {
2260                 /* (completed receiving Transfer-Encoding: chunked) */
2261                 hctx->wb_reqlen = -hctx->wb_reqlen;
2262                 if (hctx->stdin_append) {
2263                     handler_t rca = hctx->stdin_append(hctx);
2264                     if (HANDLER_GO_ON != rca) return rca;
2265                 }
2266                 else
2267                     chunkqueue_append_chunkqueue(&hctx->wb, &r->reqbody_queue);
2268             }
2269 
2270             if (0 != hctx->wb.bytes_in || -1 == hctx->wb_reqlen) {
2271                 handler_t rca = gw_write_refill_wb(hctx, r);
2272                 if (HANDLER_GO_ON != rca) return rca;
2273                 if (fdevent_fdnode_interest(hctx->fdn) & FDEVENT_OUT) {
2274                     return (rc == HANDLER_GO_ON) ? HANDLER_WAIT_FOR_EVENT : rc;
2275                 }
2276             }
2277             if (rc != HANDLER_GO_ON) return rc;
2278         }
2279     }
2280 
2281     {
2282         handler_t rc =((0==hctx->wb.bytes_in || !chunkqueue_is_empty(&hctx->wb))
2283                        && hctx->state != GW_STATE_CONNECT_DELAYED)
2284           ? gw_send_request(hctx, r)
2285           : HANDLER_WAIT_FOR_EVENT;
2286         if (HANDLER_WAIT_FOR_EVENT != rc) return rc;
2287     }
2288 
2289     if (r->conf.stream_request_body & FDEVENT_STREAM_REQUEST_TCP_FIN)
2290         gw_conditional_tcp_fin(hctx, r);
2291 
2292     return HANDLER_WAIT_FOR_EVENT;
2293 }
2294 
2295 
2296 static handler_t gw_authorizer_ok(gw_handler_ctx * const hctx, request_st * const r) {
2297     /*
2298      * If we are here in AUTHORIZER mode then a request for authorizer
2299      * was processed already, and status 200 has been returned. We need
2300      * now to handle authorized request.
2301      */
2302     char *physpath = NULL;
2303 
2304     gw_host * const host = hctx->host;
2305     if (host->docroot) {
2306         buffer_copy_buffer(&r->physical.doc_root, host->docroot);
2307         buffer_copy_buffer(&r->physical.basedir, host->docroot);
2308         buffer_copy_path_len2(&r->physical.path,
2309                               BUF_PTR_LEN(host->docroot),
2310                               BUF_PTR_LEN(&r->uri.path));
2311         physpath = r->physical.path.ptr;
2312     }
2313 
2314     /*(restore streaming flags removed during authorizer processing)*/
2315     r->conf.stream_response_body |= (hctx->opts.authorizer >> 1);
2316 
2317     gw_backend_close(hctx, r);
2318     handler_ctx_clear(hctx);
2319 
2320     /* don't do more than 6 loops here; normally shouldn't happen */
2321     if (++r->loops_per_request > 5) {
2322         log_error(r->conf.errh, __FILE__, __LINE__,
2323           "too many loops while processing request: %s",
2324           r->target_orig.ptr);
2325         r->http_status = 500; /* Internal Server Error */
2326         r->handler_module = NULL;
2327         return HANDLER_FINISHED;
2328     }
2329 
2330     /* restart the request so other handlers can process it */
2331 
2332     if (physpath) r->physical.path.ptr = NULL;
2333     http_response_reset(r); /*(includes r->http_status=0)*/
2334     /* preserve r->physical.path.ptr with modified docroot */
2335     if (physpath) r->physical.path.ptr = physpath;
2336 
2337     /*(FYI: if multiple FastCGI authorizers were to be supported,
2338      * next one could be started here instead of restarting request)*/
2339 
2340     r->handler_module = NULL;
2341     return HANDLER_COMEBACK;
2342 }
2343 
2344 
2345 __attribute_cold__
2346 static handler_t gw_recv_response_error(gw_handler_ctx * const hctx, request_st * const r, gw_proc * const proc);
2347 
2348 
2349 static handler_t gw_recv_response(gw_handler_ctx * const hctx, request_st * const r) {
2350     /*(XXX: make this a configurable flag for other protocols)*/
2351     buffer *b = (hctx->opts.backend == BACKEND_FASTCGI
2352                  || hctx->opts.backend == BACKEND_AJP13)
2353       ? chunk_buffer_acquire()
2354       : hctx->response;
2355     const off_t bytes_in = r->write_queue.bytes_in;
2356 
2357     handler_t rc = http_response_read(r, &hctx->opts, b, hctx->fdn);
2358 
2359     if (b != hctx->response) chunk_buffer_release(b);
2360 
2361     gw_proc * const proc = hctx->proc;
2362 
2363     switch (rc) {
2364     default:
2365         /* change in r->write_queue.bytes_in used to approximate backend read,
2366          * since bytes read from backend, if any, might be consumed from b by
2367          * hctx->opts->parse callback, hampering detection here.  However, this
2368          * may not be triggered for partial collection of HTTP response headers
2369          * or partial packets for backend protocol (e.g. FastCGI) */
2370         if (r->write_queue.bytes_in > bytes_in)
2371             hctx->read_ts = proc->last_used = log_monotonic_secs;
2372         return HANDLER_GO_ON;
2373     case HANDLER_FINISHED:
2374         /*hctx->read_ts =*/ proc->last_used = log_monotonic_secs;
2375 
2376         if (hctx->gw_mode == GW_AUTHORIZER
2377             && (200 == r->http_status || 0 == r->http_status))
2378             return gw_authorizer_ok(hctx, r);
2379 
2380         gw_connection_close(hctx, r);
2381         return HANDLER_FINISHED;
2382     case HANDLER_COMEBACK: /*(not expected; treat as error)*/
2383     case HANDLER_ERROR:
2384         return gw_recv_response_error(hctx, r, proc);
2385     }
2386 }
2387 
2388 
2389 __attribute_cold__
2390 static handler_t gw_recv_response_error(gw_handler_ctx * const hctx, request_st * const r, gw_proc * const proc)
2391 {
2392         /* (optimization to detect backend process exit while processing a
2393          *  large number of ready events; (this block could be removed)) */
2394         if (proc->is_local && 1 == proc->load && proc->pid == hctx->pid
2395             && proc->state != PROC_STATE_DIED
2396             && 0 == r->con->srv->srvconf.max_worker) {
2397             /* intentionally check proc->disabed_until before gw_proc_waitpid */
2398             gw_host * const host = hctx->host;
2399             log_error_st * const errh = r->con->srv->errh;
2400             if (proc->disabled_until < log_monotonic_secs
2401                 && 0 != gw_proc_waitpid(host, proc, errh)) {
2402                 if (hctx->conf.debug) {
2403                     log_error(errh, __FILE__, __LINE__,
2404                       "--- gw spawning\n\tsocket %s\n\tcurrent: 1/%d",
2405                       proc->connection_name->ptr, host->num_procs);
2406                 }
2407 
2408                 if (gw_spawn_connection(host, proc, errh, hctx->conf.debug)) {
2409                     log_error(errh, __FILE__, __LINE__,
2410                       "respawning failed, will retry later");
2411                 }
2412             }
2413         }
2414 
2415         if (r->resp_body_started == 0) {
2416             /* nothing has been sent out yet, try to use another child */
2417 
2418             if (hctx->wb.bytes_out == 0 && hctx->reconnects++ < 5) {
2419                 log_error(r->conf.errh, __FILE__, __LINE__,
2420                   "response not received, request not sent on "
2421                   "socket: %s for %s?%.*s, reconnecting",
2422                   proc->connection_name->ptr,
2423                   r->uri.path.ptr, BUFFER_INTLEN_PTR(&r->uri.query));
2424 
2425                 return gw_reconnect(hctx, r);
2426             }
2427 
2428             log_error(r->conf.errh, __FILE__, __LINE__,
2429               "response not received, request sent: %lld on "
2430               "socket: %s for %s?%.*s, closing connection",
2431               (long long)hctx->wb.bytes_out, proc->connection_name->ptr,
2432               r->uri.path.ptr, BUFFER_INTLEN_PTR(&r->uri.query));
2433         } else if (!light_btst(r->resp_htags, HTTP_HEADER_UPGRADE)
2434                    && !r->h2_connect_ext) {
2435             log_error(r->conf.errh, __FILE__, __LINE__,
2436               "response already sent out, but backend returned error on "
2437               "socket: %s for %s?%.*s, terminating connection",
2438               proc->connection_name->ptr,
2439               r->uri.path.ptr, BUFFER_INTLEN_PTR(&r->uri.query));
2440         }
2441 
2442         return gw_backend_error(hctx, r); /* HANDLER_FINISHED */
2443 }
2444 
2445 
2446 static handler_t gw_handle_fdevent(void *ctx, int revents) {
2447     gw_handler_ctx *hctx = ctx;
2448     hctx->revents |= revents;
2449     joblist_append(hctx->con);
2450     return HANDLER_FINISHED;
2451 }
2452 
2453 static handler_t gw_process_fdevent(gw_handler_ctx * const hctx, request_st * const r, int revents) {
2454     if (revents & FDEVENT_IN) {
2455         handler_t rc = gw_recv_response(hctx, r);   /*(might invalidate hctx)*/
2456         if (rc != HANDLER_GO_ON) return rc;         /*(unless HANDLER_GO_ON)*/
2457     }
2458 
2459     if (revents & FDEVENT_OUT) {
2460         return gw_send_request(hctx, r); /*(might invalidate hctx)*/
2461     }
2462 
2463     /* perhaps this issue is already handled */
2464     if (revents & (FDEVENT_HUP|FDEVENT_RDHUP)) {
2465         if (hctx->state == GW_STATE_CONNECT_DELAYED) {
2466             return gw_send_request(hctx, r); /*(might invalidate hctx)*/
2467         } else if (r->resp_body_started) {
2468             /* drain any remaining data from kernel pipe buffers
2469              * even if (r->conf.stream_response_body
2470              *          & FDEVENT_STREAM_RESPONSE_BUFMIN)
2471              * since event loop will spin on fd FDEVENT_HUP event
2472              * until unregistered. */
2473             handler_t rc;
2474             const unsigned short flags = r->conf.stream_response_body;
2475             r->conf.stream_response_body &= ~FDEVENT_STREAM_RESPONSE_BUFMIN;
2476             r->conf.stream_response_body |= FDEVENT_STREAM_RESPONSE_POLLRDHUP;
2477             do {
2478                 rc = gw_recv_response(hctx, r);  /*(might invalidate hctx)*/
2479             } while (rc == HANDLER_GO_ON);       /*(unless HANDLER_GO_ON)*/
2480             r->conf.stream_response_body = flags;
2481             return rc; /* HANDLER_FINISHED or HANDLER_ERROR */
2482         } else {
2483             gw_proc *proc = hctx->proc;
2484             log_error(r->conf.errh, __FILE__, __LINE__,
2485               "error: unexpected close of gw connection for %s?%.*s "
2486               "(no gw process on socket: %s ?) %d",
2487               r->uri.path.ptr, BUFFER_INTLEN_PTR(&r->uri.query),
2488               proc->connection_name->ptr, hctx->state);
2489 
2490             gw_connection_close(hctx, r);
2491             return HANDLER_FINISHED;
2492         }
2493     } else if (revents & FDEVENT_ERR) {
2494         log_error(r->conf.errh, __FILE__, __LINE__,
2495           "gw: got a FDEVENT_ERR. Don't know why.");
2496         return gw_backend_error(hctx, r); /* HANDLER_FINISHED */
2497     }
2498 
2499     return HANDLER_GO_ON;
2500 }
2501 
2502 handler_t gw_check_extension(request_st * const r, gw_plugin_data * const p, int uri_path_handler, size_t hctx_sz) {
2503   #if 0 /*(caller must handle)*/
2504     if (NULL != r->handler_module) return HANDLER_GO_ON;
2505     gw_patch_connection(r, p);
2506     if (NULL == p->conf.exts) return HANDLER_GO_ON;
2507   #endif
2508 
2509     buffer *fn = uri_path_handler ? &r->uri.path : &r->physical.path;
2510     const size_t s_len = buffer_clen(fn);
2511     gw_extension *extension = NULL;
2512     gw_host *host = NULL;
2513     gw_handler_ctx *hctx;
2514     unsigned short gw_mode;
2515 
2516     if (0 == s_len) return HANDLER_GO_ON; /*(not expected)*/
2517 
2518     /* check p->conf.exts_auth list and then p->conf.ext_resp list
2519      * (skip p->conf.exts_auth if array is empty
2520      *  or if GW_AUTHORIZER already ran in this request) */
2521     hctx = r->plugin_ctx[p->id];
2522     /*(hctx not NULL if GW_AUTHORIZER ran; hctx->ext_auth check is redundant)*/
2523     gw_mode = (NULL == hctx || NULL == hctx->ext_auth)
2524       ? 0              /*GW_AUTHORIZER p->conf.exts_auth will be searched next*/
2525       : GW_AUTHORIZER; /*GW_RESPONDER p->conf.exts_resp will be searched next*/
2526 
2527     do {
2528 
2529         gw_exts *exts;
2530         if (0 == gw_mode) {
2531             gw_mode = GW_AUTHORIZER;
2532             exts = p->conf.exts_auth;
2533         } else {
2534             gw_mode = GW_RESPONDER;
2535             exts = p->conf.exts_resp;
2536         }
2537 
2538         if (0 == exts->used) continue;
2539 
2540         /* gw.map-extensions maps extensions to existing gw.server entries
2541          *
2542          * gw.map-extensions = ( ".php3" => ".php" )
2543          *
2544          * gw.server = ( ".php" => ... )
2545          *
2546          * */
2547 
2548         /* check if extension-mapping matches */
2549         if (p->conf.ext_mapping) {
2550             data_string *ds =
2551               (data_string *)array_match_key_suffix(p->conf.ext_mapping, fn);
2552             if (NULL != ds) {
2553                 /* found a mapping */
2554                     /* check if we know the extension */
2555                     uint32_t k;
2556                     for (k = 0; k < exts->used; ++k) {
2557                         extension = exts->exts+k;
2558 
2559                         if (buffer_is_equal(&ds->value, &extension->key)) {
2560                             break;
2561                         }
2562                     }
2563 
2564                     if (k == exts->used) {
2565                         /* found nothing */
2566                         extension = NULL;
2567                     }
2568             }
2569         }
2570 
2571         if (extension == NULL) {
2572             size_t uri_path_len = buffer_clen(&r->uri.path);
2573 
2574             /* check if extension matches */
2575             for (uint32_t k = 0; k < exts->used; ++k) {
2576                 gw_extension *ext = exts->exts+k;
2577               #ifdef __clang_analyzer__
2578                 force_assert(ext); /*(unnecessary; quiet clang analyzer)*/
2579               #endif
2580                 size_t ct_len = buffer_clen(&ext->key);
2581 
2582                 /* check _url_ in the form "/gw_pattern" */
2583                 if (ext->key.ptr[0] == '/') {
2584                     if (ct_len <= uri_path_len
2585                         && 0 == memcmp(r->uri.path.ptr, ext->key.ptr, ct_len)) {
2586                         extension = ext;
2587                         break;
2588                     }
2589                 } else if (ct_len <= s_len
2590                            && 0 == memcmp(fn->ptr + s_len - ct_len,
2591                                           ext->key.ptr, ct_len)) {
2592                     /* check extension in the form ".fcg" */
2593                     extension = ext;
2594                     break;
2595                 }
2596             }
2597         }
2598 
2599     } while (NULL == extension && gw_mode != GW_RESPONDER);
2600 
2601     /* extension doesn't match */
2602     if (NULL == extension) {
2603         return HANDLER_GO_ON;
2604     }
2605 
2606     /* check if we have at least one server for this extension up and running */
2607     host = gw_host_get(r, extension, p->conf.balance, p->conf.debug);
2608     if (NULL == host) {
2609         return HANDLER_FINISHED;
2610     }
2611 
2612     /* a note about no handler is not sent yet */
2613     extension->note_is_sent = 0;
2614 
2615     /*
2616      * if check-local is disabled, use the uri.path handler
2617      *
2618      */
2619 
2620     /* init handler-context */
2621     if (uri_path_handler) {
2622         if (host->check_local)
2623             return HANDLER_GO_ON;
2624 
2625         /* path info rewrite is done only for /prefix/? matches */
2626         /* do not split path info for authorizer */
2627         if (extension->key.ptr[0] == '/' && gw_mode != GW_AUTHORIZER) {
2628                 /* the prefix is the SCRIPT_NAME,
2629                 * everything from start to the next slash
2630                 * this is important for check-local = "disable"
2631                 *
2632                 * if prefix = /admin.gw
2633                 *
2634                 * /admin.gw/foo/bar
2635                 *
2636                 * SCRIPT_NAME = /admin.gw
2637                 * PATH_INFO   = /foo/bar
2638                 *
2639                 * if prefix = /cgi-bin/
2640                 *
2641                 * /cgi-bin/foo/bar
2642                 *
2643                 * SCRIPT_NAME = /cgi-bin/foo
2644                 * PATH_INFO   = /bar
2645                 *
2646                 * if prefix = /, and fix-root-path-name is enable
2647                 *
2648                 * /cgi-bin/foo/bar
2649                 *
2650                 * SCRIPT_NAME = /cgi-bin/foo
2651                 * PATH_INFO   = /bar
2652                 *
2653                 */
2654             /* (s_len is buffer_clen(&r->uri.path) if (uri_path_handler) */
2655             uint32_t elen = buffer_clen(&extension->key);
2656             const char *pathinfo;
2657             if (1 == elen && host->fix_root_path_name) {
2658                 buffer_copy_buffer(&r->pathinfo, &r->uri.path);
2659                 buffer_truncate(&r->uri.path, 0);
2660             }
2661             else if (s_len > elen
2662                      && (pathinfo = strchr(r->uri.path.ptr+elen, '/'))) {
2663                 /* rewrite uri.path and pathinfo */
2664                 const uint32_t plen = r->uri.path.ptr + s_len - pathinfo;
2665                 buffer_copy_string_len(&r->pathinfo, pathinfo, plen);
2666                 buffer_truncate(&r->uri.path, s_len - plen);
2667             }
2668         }
2669     }
2670 
2671     if (!hctx) hctx = handler_ctx_init(hctx_sz);
2672 
2673     hctx->ev               = r->con->srv->ev;
2674     hctx->r                = r;
2675     hctx->con              = r->con;
2676     hctx->plugin_data      = p;
2677     hctx->host             = host;
2678     hctx->proc             = NULL;
2679     hctx->ext              = extension;
2680     gw_host_assign(host);
2681 
2682     hctx->gw_mode = gw_mode;
2683     if (gw_mode == GW_AUTHORIZER) {
2684         hctx->ext_auth = hctx->ext;
2685     }
2686 
2687     /*hctx->conf.exts        = p->conf.exts;*/
2688     /*hctx->conf.exts_auth   = p->conf.exts_auth;*/
2689     /*hctx->conf.exts_resp   = p->conf.exts_resp;*/
2690     /*hctx->conf.ext_mapping = p->conf.ext_mapping;*/
2691     hctx->conf.balance     = p->conf.balance;
2692     hctx->conf.proto       = p->conf.proto;
2693     hctx->conf.debug       = p->conf.debug;
2694 
2695     hctx->opts.max_per_read =
2696       !(r->conf.stream_response_body /*(if not streaming response body)*/
2697         & (FDEVENT_STREAM_RESPONSE|FDEVENT_STREAM_RESPONSE_BUFMIN))
2698         ? 262144
2699         : (r->conf.stream_response_body & FDEVENT_STREAM_RESPONSE_BUFMIN)
2700           ? 16384  /* FDEVENT_STREAM_RESPONSE_BUFMIN */
2701           : 65536; /* FDEVENT_STREAM_RESPONSE */
2702     hctx->opts.fdfmt = S_IFSOCK;
2703     hctx->opts.authorizer = (gw_mode == GW_AUTHORIZER);
2704     hctx->opts.local_redir = 0;
2705     hctx->opts.xsendfile_allow = host->xsendfile_allow;
2706     hctx->opts.xsendfile_docroot = host->xsendfile_docroot;
2707 
2708     r->plugin_ctx[p->id] = hctx;
2709 
2710     r->handler_module = p->self;
2711 
2712     if (r->conf.log_request_handling) {
2713         log_error(r->conf.errh, __FILE__, __LINE__,
2714                   "handling the request using %s", p->self->name);
2715     }
2716 
2717     return HANDLER_GO_ON;
2718 }
2719 
2720 __attribute_cold__
2721 __attribute_noinline__
2722 static void gw_handle_trigger_hctx_timeout(gw_handler_ctx * const hctx, const char * const msg) {
2723 
2724     request_st * const r = hctx->r;
2725     joblist_append(r->con);
2726 
2727     if (*msg == 'c') { /* "connect" */
2728         /* temporarily disable backend proc */
2729         gw_proc_connect_error(r, hctx->host, hctx->proc, hctx->pid,
2730                               ETIMEDOUT, hctx->conf.debug);
2731         /* cleanup this request and let request handler start request again */
2732         /* retry only once since request already waited write_timeout secs */
2733         if (hctx->reconnects++ < 1) {
2734             gw_reconnect(hctx, r);
2735             return;
2736         }
2737         r->http_status = 503; /* Service Unavailable */
2738     }
2739     else { /* "read" or "write" */
2740         /* blocked waiting to send (more) data to or to receive response
2741          * (neither are a definite indication that the proc is no longer
2742          *  responsive on other socket connections; not marking proc overloaded)
2743          * (If connect() to backend succeeded, then we began sending
2744          *  request and filled kernel socket buffers, so request is
2745          *  in progress and it is not safe or possible to retry) */
2746         /*if (hctx->conf.debug)*/
2747             log_error(r->conf.errh, __FILE__, __LINE__,
2748               "%s timeout on socket: %s (fd: %d)",
2749               msg, hctx->proc->connection_name->ptr, hctx->fd);
2750 
2751         if (*msg == 'w') { /* "write" */
2752             gw_write_error(hctx, r); /*(calls gw_backend_error())*/
2753             if (r->http_status == 503) r->http_status = 504; /*Gateway Timeout*/
2754             return;
2755         } /* else "read" */
2756     }
2757     gw_backend_error(hctx, r);
2758     if (r->http_status == 500 && !r->resp_body_started && !r->handler_module)
2759         r->http_status = 504; /*Gateway Timeout*/
2760 }
2761 
2762 __attribute_noinline__
2763 static void gw_handle_trigger_host_timeouts(gw_host * const host) {
2764 
2765     if (NULL == host->hctxs) return;
2766     const unix_time64_t rsecs = (unix_time64_t)host->read_timeout;
2767     const unix_time64_t wsecs = (unix_time64_t)host->write_timeout;
2768     const unix_time64_t csecs = (unix_time64_t)host->connect_timeout;
2769     if (!rsecs && !wsecs && !csecs)
2770         return; /*(no timeout policy (default))*/
2771 
2772     const unix_time64_t mono = log_monotonic_secs; /*(could have callers pass)*/
2773     for (gw_handler_ctx *hctx = host->hctxs, *next; hctx; hctx = next) {
2774         /* if timeout occurs, hctx might be invalidated and removed from list,
2775          * so next element must be store before checking for timeout */
2776         next = hctx->next;
2777 
2778         if (hctx->state == GW_STATE_CONNECT_DELAYED) {
2779             if (mono - hctx->write_ts > csecs && csecs) /*(waiting for write)*/
2780                 gw_handle_trigger_hctx_timeout(hctx, "connect");
2781             continue; /*(do not apply wsecs below to GW_STATE_CONNECT_DELAYED)*/
2782         }
2783 
2784         const int events = fdevent_fdnode_interest(hctx->fdn);
2785         if ((events & FDEVENT_IN) && mono - hctx->read_ts > rsecs && rsecs) {
2786             gw_handle_trigger_hctx_timeout(hctx, "read");
2787             continue;
2788         }
2789         if ((events & FDEVENT_OUT) && mono - hctx->write_ts > wsecs && wsecs) {
2790             gw_handle_trigger_hctx_timeout(hctx, "write");
2791             continue;
2792         }
2793     }
2794 }
2795 
2796 static void gw_handle_trigger_host(gw_host * const host, log_error_st * const errh, const int debug) {
2797 
2798     /* check for socket timeouts on active requests to backend host */
2799     gw_handle_trigger_host_timeouts(host);
2800 
2801     /* check each child proc to detect if proc exited */
2802 
2803     gw_proc *proc;
2804     unix_time64_t idle_timestamp;
2805     int overload = 1;
2806 
2807   #if 0 /* redundant w/ handle_waitpid hook since lighttpd 1.4.46 */
2808     for (proc = host->first; proc; proc = proc->next) {
2809         gw_proc_waitpid(host, proc, errh);
2810     }
2811   #endif
2812 
2813     gw_restart_dead_procs(host, errh, debug, 1);
2814 
2815     /* check if adaptive spawning enabled */
2816     if (host->min_procs == host->max_procs) return;
2817     if (!host->bin_path) return;
2818 
2819     for (proc = host->first; proc; proc = proc->next) {
2820         if (proc->load <= host->max_load_per_proc) {
2821             overload = 0;
2822             break;
2823         }
2824     }
2825 
2826     if (overload && host->num_procs && host->num_procs < host->max_procs) {
2827         /* overload, spawn new child */
2828         if (debug) {
2829             log_error(errh, __FILE__, __LINE__,
2830               "overload detected, spawning a new child");
2831         }
2832 
2833         gw_proc_spawn(host, errh, debug);
2834     }
2835 
2836     idle_timestamp = log_monotonic_secs - host->idle_timeout;
2837     for (proc = host->first; proc; proc = proc->next) {
2838         if (host->num_procs <= host->min_procs) break;
2839         if (0 != proc->load) continue;
2840         if (proc->pid <= 0) continue;
2841         if (proc->last_used >= idle_timestamp) continue;
2842 
2843         /* terminate proc that has been idling for a long time */
2844         if (debug) {
2845             log_error(errh, __FILE__, __LINE__,
2846               "idle-timeout reached, terminating child: socket: %s pid %d",
2847               proc->unixsocket ? proc->unixsocket->ptr : "", proc->pid);
2848         }
2849 
2850         gw_proc_kill(host, proc);
2851 
2852         /* proc is now in unused, let next second handle next process */
2853         break;
2854     }
2855 
2856   #if 0 /* redundant w/ handle_waitpid hook since lighttpd 1.4.46 */
2857     for (proc = host->unused_procs; proc; proc = proc->next) {
2858         gw_proc_waitpid(host, proc, errh);
2859     }
2860   #endif
2861 }
2862 
2863 static void gw_handle_trigger_exts(gw_exts * const exts, log_error_st * const errh, const int debug) {
2864     for (uint32_t j = 0; j < exts->used; ++j) {
2865         gw_extension *ex = exts->exts+j;
2866         for (uint32_t n = 0; n < ex->used; ++n) {
2867             gw_handle_trigger_host(ex->hosts[n], errh, debug);
2868         }
2869     }
2870 }
2871 
2872 static void gw_handle_trigger_exts_wkr(gw_exts *exts, log_error_st *errh) {
2873     for (uint32_t j = 0; j < exts->used; ++j) {
2874         gw_extension * const ex = exts->exts+j;
2875         for (uint32_t n = 0; n < ex->used; ++n) {
2876             gw_host * const host = ex->hosts[n];
2877             gw_handle_trigger_host_timeouts(host);
2878             for (gw_proc *proc = host->first; proc; proc = proc->next) {
2879                 if (proc->state == PROC_STATE_OVERLOADED)
2880                     gw_proc_check_enable(host, proc, errh);
2881             }
2882         }
2883     }
2884 }
2885 
2886 handler_t gw_handle_trigger(server *srv, void *p_d) {
2887     gw_plugin_data * const p = p_d;
2888     int wkr = (0 != srv->srvconf.max_worker && p->srv_pid != srv->pid);
2889     log_error_st * const errh = srv->errh;
2890     int global_debug = 0;
2891 
2892     if (NULL == p->cvlist) return HANDLER_GO_ON;
2893     /* (init i to 0 if global context; to 1 to skip empty global context) */
2894     for (int i = !p->cvlist[0].v.u2[1], used = p->nconfig; i < used; ++i) {
2895         config_plugin_value_t *cpv = p->cvlist + p->cvlist[i].v.u2[0];
2896         gw_plugin_config *conf = NULL;
2897         int debug = global_debug;
2898         for (; -1 != cpv->k_id; ++cpv) {
2899             switch (cpv->k_id) {
2900               case 0: /* xxxxx.server */
2901                 if (cpv->vtype == T_CONFIG_LOCAL) conf = cpv->v.v;
2902                 break;
2903               case 2: /* xxxxx.debug */
2904                 debug = (int)cpv->v.u;
2905                 if (0 == i) global_debug = (int)cpv->v.u;
2906               default:
2907                 break;
2908             }
2909         }
2910 
2911         if (NULL == conf || NULL == conf->exts) continue;
2912 
2913         /* (debug flag is only active if set in same scope as xxxxx.server
2914          *  or global scope (for convenience))
2915          * (unable to use p->defaults.debug since gw_plugin_config
2916          *  might be part of a larger plugin_config) */
2917         wkr
2918           ? gw_handle_trigger_exts_wkr(conf->exts, errh)
2919           : gw_handle_trigger_exts(conf->exts, errh, debug);
2920     }
2921 
2922     return HANDLER_GO_ON;
2923 }
2924 
2925 handler_t gw_handle_waitpid_cb(server *srv, void *p_d, pid_t pid, int status) {
2926     gw_plugin_data * const p = p_d;
2927     if (0 != srv->srvconf.max_worker && p->srv_pid != srv->pid)
2928         return HANDLER_GO_ON;
2929     log_error_st * const errh = srv->errh;
2930     int global_debug = 0;
2931 
2932     if (NULL == p->cvlist) return HANDLER_GO_ON;
2933     /* (init i to 0 if global context; to 1 to skip empty global context) */
2934     for (int i = !p->cvlist[0].v.u2[1], used = p->nconfig; i < used; ++i) {
2935         config_plugin_value_t *cpv = p->cvlist + p->cvlist[i].v.u2[0];
2936         gw_plugin_config *conf = NULL;
2937         int debug = global_debug;
2938         for (; -1 != cpv->k_id; ++cpv) {
2939             switch (cpv->k_id) {
2940               case 0: /* xxxxx.server */
2941                 if (cpv->vtype == T_CONFIG_LOCAL) conf = cpv->v.v;
2942                 break;
2943               case 2: /* xxxxx.debug */
2944                 debug = (int)cpv->v.u;
2945                 if (0 == i) global_debug = (int)cpv->v.u;
2946               default:
2947                 break;
2948             }
2949         }
2950 
2951         if (NULL == conf || NULL == conf->exts) continue;
2952 
2953         /* (debug flag is only active if set in same scope as xxxxx.server
2954          *  or global scope (for convenience))
2955          * (unable to use p->defaults.debug since gw_plugin_config
2956          *  might be part of a larger plugin_config) */
2957         const unix_time64_t cur_ts = log_monotonic_secs;
2958         gw_exts *exts = conf->exts;
2959         for (uint32_t j = 0; j < exts->used; ++j) {
2960             gw_extension *ex = exts->exts+j;
2961             for (uint32_t n = 0; n < ex->used; ++n) {
2962                 gw_host *host = ex->hosts[n];
2963                 gw_proc *proc;
2964                 for (proc = host->first; proc; proc = proc->next) {
2965                     if (!proc->is_local || proc->pid != pid) continue;
2966 
2967                     gw_proc_waitpid_log(host, proc, errh, status);
2968                     gw_proc_set_state(host, proc, PROC_STATE_DIED);
2969                     proc->pid = 0;
2970 
2971                     /* restart, but avoid spinning if child exits too quickly */
2972                     if (proc->disabled_until < cur_ts) {
2973                         /*(set state PROC_STATE_DIED above, so != KILLED here)*/
2974                         /*(PROC_STATE_KILLED belongs in unused_procs, anyway)*/
2975                         if (proc->state != PROC_STATE_KILLED)
2976                             proc->disabled_until = cur_ts;
2977                         if (gw_spawn_connection(host, proc, errh, debug)) {
2978                             log_error(errh, __FILE__, __LINE__,
2979                               "ERROR: spawning gw failed.");
2980                         }
2981                     }
2982 
2983                     return HANDLER_FINISHED;
2984                 }
2985                 for (proc = host->unused_procs; proc; proc = proc->next) {
2986                     if (!proc->is_local || proc->pid != pid) continue;
2987 
2988                     gw_proc_waitpid_log(host, proc, errh, status);
2989                     if (proc->state != PROC_STATE_KILLED)
2990                         proc->disabled_until = cur_ts;
2991                     gw_proc_set_state(host, proc, PROC_STATE_DIED);
2992                     proc->pid = 0;
2993                     return HANDLER_FINISHED;
2994                 }
2995             }
2996         }
2997     }
2998 
2999     return HANDLER_GO_ON;
3000 }
3001