summaryrefslogtreecommitdiff
path: root/data/lighttpd/lighttpd-1.4.53/src/gw_backend.c
diff options
context:
space:
mode:
authorMCApollo <34170230+MCApollo@users.noreply.github.com>2019-04-29 17:53:00 +0000
committerMCApollo <34170230+MCApollo@users.noreply.github.com>2019-04-29 17:53:00 +0000
commit59f5fd20e2f59ae186d5a461aef7782dd55e4e7b (patch)
tree10475faf0b6dcb812b64da679f4ae8ff01c45874 /data/lighttpd/lighttpd-1.4.53/src/gw_backend.c
parentf06e297cd175cca39745f6d1970225ae88aa68f1 (diff)
Updated lighttpd.
Diffstat (limited to 'data/lighttpd/lighttpd-1.4.53/src/gw_backend.c')
-rw-r--r--data/lighttpd/lighttpd-1.4.53/src/gw_backend.c2604
1 files changed, 2604 insertions, 0 deletions
diff --git a/data/lighttpd/lighttpd-1.4.53/src/gw_backend.c b/data/lighttpd/lighttpd-1.4.53/src/gw_backend.c
new file mode 100644
index 000000000..62183febb
--- /dev/null
+++ b/data/lighttpd/lighttpd-1.4.53/src/gw_backend.c
@@ -0,0 +1,2604 @@
+#include "first.h"
+
+#include "gw_backend.h"
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include "sys-socket.h"
+#ifdef HAVE_SYS_UIO_H
+#include <sys/uio.h>
+#endif
+#ifdef HAVE_SYS_WAIT_H
+#include <sys/wait.h>
+#endif
+
+#include <errno.h>
+#include <fcntl.h>
+#include <limits.h>
+#include <stdlib.h>
+#include <signal.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "base.h"
+#include "array.h"
+#include "buffer.h"
+#include "crc32.h"
+#include "fdevent.h"
+#include "log.h"
+#include "sock_addr.h"
+
+
+
+
+#include "status_counter.h"
+
+static int * gw_status_get_counter(server *srv, gw_host *host, gw_proc *proc, const char *tag, size_t len) {
+ buffer *b = srv->tmp_buf;
+ buffer_copy_string_len(b, CONST_STR_LEN("gw.backend."));
+ buffer_append_string_buffer(b, host->id);
+ if (proc) {
+ buffer_append_string_len(b, CONST_STR_LEN("."));
+ buffer_append_int(b, proc->id);
+ }
+ buffer_append_string_len(b, tag, len);
+ return status_counter_get_counter(srv, CONST_BUF_LEN(b));
+}
+
+static void gw_proc_tag_inc(server *srv, gw_host *host, gw_proc *proc, const char *tag, size_t len) {
+ ++(*gw_status_get_counter(srv, host, proc, tag, len));
+}
+
+static void gw_proc_load_inc(server *srv, gw_host *host, gw_proc *proc) {
+ *gw_status_get_counter(srv,host,proc,CONST_STR_LEN(".load")) = ++proc->load;
+
+ status_counter_inc(srv, CONST_STR_LEN("gw.active-requests"));
+}
+
+static void gw_proc_load_dec(server *srv, gw_host *host, gw_proc *proc) {
+ *gw_status_get_counter(srv,host,proc,CONST_STR_LEN(".load")) = --proc->load;
+
+ status_counter_dec(srv, CONST_STR_LEN("gw.active-requests"));
+}
+
+static void gw_host_assign(server *srv, gw_host *host) {
+ *gw_status_get_counter(srv,host,NULL,CONST_STR_LEN(".load")) = ++host->load;
+}
+
+static void gw_host_reset(server *srv, gw_host *host) {
+ *gw_status_get_counter(srv,host,NULL,CONST_STR_LEN(".load")) = --host->load;
+}
+
+static int gw_status_init(server *srv, gw_host *host, gw_proc *proc) {
+ *gw_status_get_counter(srv, host, proc, CONST_STR_LEN(".disabled")) = 0;
+ *gw_status_get_counter(srv, host, proc, CONST_STR_LEN(".died")) = 0;
+ *gw_status_get_counter(srv, host, proc, CONST_STR_LEN(".overloaded")) = 0;
+ *gw_status_get_counter(srv, host, proc, CONST_STR_LEN(".connected")) = 0;
+ *gw_status_get_counter(srv, host, proc, CONST_STR_LEN(".load")) = 0;
+
+ *gw_status_get_counter(srv, host, NULL, CONST_STR_LEN(".load")) = 0;
+
+ return 0;
+}
+
+
+
+
+static void gw_proc_set_state(gw_host *host, gw_proc *proc, int state) {
+ if ((int)proc->state == state) return;
+ if (proc->state == PROC_STATE_RUNNING) {
+ --host->active_procs;
+ } else if (state == PROC_STATE_RUNNING) {
+ ++host->active_procs;
+ }
+ proc->state = state;
+}
+
+
+static gw_proc *gw_proc_init(void) {
+ gw_proc *f = calloc(1, sizeof(*f));
+ force_assert(f);
+
+ f->unixsocket = buffer_init();
+ f->connection_name = buffer_init();
+
+ f->prev = NULL;
+ f->next = NULL;
+ f->state = PROC_STATE_DIED;
+
+ return f;
+}
+
+static void gw_proc_free(gw_proc *f) {
+ if (!f) return;
+
+ gw_proc_free(f->next);
+
+ buffer_free(f->unixsocket);
+ buffer_free(f->connection_name);
+ free(f->saddr);
+
+ free(f);
+}
+
+static gw_host *gw_host_init(void) {
+ gw_host *f = calloc(1, sizeof(*f));
+ force_assert(f);
+
+ f->id = buffer_init();
+ f->host = buffer_init();
+ f->unixsocket = buffer_init();
+ f->docroot = buffer_init();
+ f->bin_path = buffer_init();
+ f->bin_env = array_init();
+ f->bin_env_copy = array_init();
+ f->strip_request_uri = buffer_init();
+ f->xsendfile_docroot = array_init();
+
+ return f;
+}
+
+static void gw_host_free(gw_host *h) {
+ if (!h) return;
+ if (h->refcount) {
+ --h->refcount;
+ return;
+ }
+
+ buffer_free(h->id);
+ buffer_free(h->host);
+ buffer_free(h->unixsocket);
+ buffer_free(h->docroot);
+ buffer_free(h->bin_path);
+ buffer_free(h->strip_request_uri);
+ array_free(h->bin_env);
+ array_free(h->bin_env_copy);
+ array_free(h->xsendfile_docroot);
+
+ gw_proc_free(h->first);
+ gw_proc_free(h->unused_procs);
+
+ for (size_t i = 0; i < h->args.used; ++i) free(h->args.ptr[i]);
+ free(h->args.ptr);
+ free(h);
+}
+
+static gw_exts *gw_extensions_init(void) {
+ gw_exts *f = calloc(1, sizeof(*f));
+ force_assert(f);
+ return f;
+}
+
+static void gw_extensions_free(gw_exts *f) {
+ if (!f) return;
+ for (size_t i = 0; i < f->used; ++i) {
+ gw_extension *fe = f->exts[i];
+ for (size_t j = 0; j < fe->used; ++j) {
+ gw_host_free(fe->hosts[j]);
+ }
+ buffer_free(fe->key);
+ free(fe->hosts);
+ free(fe);
+ }
+ free(f->exts);
+ free(f);
+}
+
+static int gw_extension_insert(gw_exts *ext, buffer *key, gw_host *fh) {
+ gw_extension *fe = NULL;
+ for (size_t i = 0; i < ext->used; ++i) {
+ if (buffer_is_equal(key, ext->exts[i]->key)) {
+ fe = ext->exts[i];
+ break;
+ }
+ }
+
+ if (NULL == fe) {
+ fe = calloc(1, sizeof(*fe));
+ force_assert(fe);
+ fe->key = buffer_init();
+ fe->last_used_ndx = -1;
+ buffer_copy_buffer(fe->key, key);
+
+ if (ext->size == 0) {
+ ext->size = 8;
+ ext->exts = malloc(ext->size * sizeof(*(ext->exts)));
+ force_assert(ext->exts);
+ } else if (ext->used == ext->size) {
+ ext->size += 8;
+ ext->exts = realloc(ext->exts, ext->size * sizeof(*(ext->exts)));
+ force_assert(ext->exts);
+ }
+ ext->exts[ext->used++] = fe;
+ fe->size = 4;
+ fe->hosts = malloc(fe->size * sizeof(*(fe->hosts)));
+ force_assert(fe->hosts);
+ } else if (fe->size == fe->used) {
+ fe->size += 4;
+ fe->hosts = realloc(fe->hosts, fe->size * sizeof(*(fe->hosts)));
+ force_assert(fe->hosts);
+ }
+
+ fe->hosts[fe->used++] = fh;
+ return 0;
+}
+
+static void gw_proc_connect_success(server *srv, gw_host *host, gw_proc *proc, int debug) {
+ gw_proc_tag_inc(srv, host, proc, CONST_STR_LEN(".connected"));
+ proc->last_used = srv->cur_ts;
+
+ if (debug) {
+ log_error_write(srv, __FILE__, __LINE__, "ssdsbsd",
+ "got proc:",
+ "pid:", proc->pid,
+ "socket:", proc->connection_name,
+ "load:", proc->load);
+ }
+}
+
+static void gw_proc_connect_error(server *srv, gw_host *host, gw_proc *proc, pid_t pid, int errnum, int debug) {
+ log_error_write(srv, __FILE__, __LINE__, "sssb",
+ "establishing connection failed:", strerror(errnum),
+ "socket:", proc->connection_name);
+
+ if (!proc->is_local) {
+ proc->disabled_until = srv->cur_ts + host->disable_time;
+ gw_proc_set_state(host, proc, PROC_STATE_OVERLOADED);
+ }
+ else if (proc->pid == pid && proc->state == PROC_STATE_RUNNING) {
+ /* several requests from lighttpd might reference the same proc
+ *
+ * Only one of them should mark the proc
+ * and all other ones should just take a new one.
+ *
+ * If a new proc was started with the old struct, this might
+ * otherwise lead to marking a perfectly good proc as dead
+ */
+ log_error_write(srv, __FILE__, __LINE__, "sdssd",
+ "backend error; we'll disable for", host->disable_time,
+ "secs and send the request to another backend instead:",
+ "load:", host->load);
+ if (EAGAIN == errnum) {
+ /* - EAGAIN: cool down the backend; it is overloaded */
+ #ifdef __linux__
+ log_error_write(srv, __FILE__, __LINE__, "s",
+ "If this happened on Linux: You have run out of local ports. "
+ "Check the manual, section Performance how to handle this.");
+ #endif
+ if (debug) {
+ log_error_write(srv, __FILE__, __LINE__, "sbsd",
+ "This means that you have more incoming requests than your "
+ "FastCGI backend can handle in parallel. It might help to "
+ "spawn more FastCGI backends or PHP children; if not, "
+ "decrease server.max-connections. The load for this FastCGI "
+ "backend", proc->connection_name, "is", proc->load);
+ }
+ proc->disabled_until = srv->cur_ts + host->disable_time;
+ gw_proc_set_state(host, proc, PROC_STATE_OVERLOADED);
+ }
+ else {
+ /* we got a hard error from the backend like
+ * - ECONNREFUSED for tcp-ip sockets
+ * - ENOENT for unix-domain-sockets
+ */
+ #if 0
+ gw_proc_set_state(host, proc, PROC_STATE_DIED_WAIT_FOR_PID);
+ #else /* treat as overloaded (future: unless we send kill() signal)*/
+ proc->disabled_until = srv->cur_ts + host->disable_time;
+ gw_proc_set_state(host, proc, PROC_STATE_OVERLOADED);
+ #endif
+ }
+ }
+
+ if (EAGAIN == errnum) {
+ gw_proc_tag_inc(srv, host, proc, CONST_STR_LEN(".overloaded"));
+ }
+ else {
+ gw_proc_tag_inc(srv, host, proc, CONST_STR_LEN(".died"));
+ }
+}
+
+static void gw_proc_release(server *srv, gw_host *host, gw_proc *proc, int debug) {
+ gw_proc_load_dec(srv, host, proc);
+
+ if (debug) {
+ log_error_write(srv, __FILE__, __LINE__, "ssdsbsd",
+ "released proc:",
+ "pid:", proc->pid,
+ "socket:", proc->connection_name,
+ "load:", proc->load);
+ }
+}
+
+static void gw_proc_check_enable(server *srv, gw_host *host, gw_proc *proc) {
+ if (srv->cur_ts <= proc->disabled_until) return;
+ if (proc->state != PROC_STATE_OVERLOADED) return;
+
+ gw_proc_set_state(host, proc, PROC_STATE_RUNNING);
+
+ log_error_write(srv, __FILE__, __LINE__, "sbbdb",
+ "gw-server re-enabled:", proc->connection_name,
+ host->host, host->port, host->unixsocket);
+}
+
+static void gw_proc_waitpid_log(server *srv, gw_host *host, gw_proc *proc, int status) {
+ UNUSED(host);
+ if (WIFEXITED(status)) {
+ if (proc->state != PROC_STATE_KILLED) {
+ log_error_write(srv, __FILE__, __LINE__, "sdb",
+ "child exited:",
+ WEXITSTATUS(status), proc->connection_name);
+ }
+ } else if (WIFSIGNALED(status)) {
+ if (WTERMSIG(status) != SIGTERM && WTERMSIG(status) != SIGINT
+ && WTERMSIG(status) != host->kill_signal) {
+ log_error_write(srv, __FILE__, __LINE__, "sd",
+ "child signalled:", WTERMSIG(status));
+ }
+ } else {
+ log_error_write(srv, __FILE__, __LINE__, "sd",
+ "child died somehow:", status);
+ }
+}
+
+static int gw_proc_waitpid(server *srv, gw_host *host, gw_proc *proc) {
+ int rc, status;
+
+ if (!proc->is_local) return 0;
+ if (proc->pid <= 0) return 0;
+
+ do {
+ rc = waitpid(proc->pid, &status, WNOHANG);
+ } while (-1 == rc && errno == EINTR);
+ if (0 == rc) return 0; /* child still running */
+
+ /* child terminated */
+ if (-1 == rc) {
+ /* EINVAL or ECHILD no child processes */
+ /* should not happen; someone else has cleaned up for us */
+ log_error_write(srv, __FILE__, __LINE__, "sddss",
+ "pid ", proc->pid, proc->state,
+ "not found:", strerror(errno));
+ }
+ else {
+ gw_proc_waitpid_log(srv, host, proc, status);
+ }
+
+ proc->pid = 0;
+ if (proc->state != PROC_STATE_KILLED)
+ proc->disabled_until = srv->cur_ts;
+ gw_proc_set_state(host, proc, PROC_STATE_DIED);
+ return 1;
+}
+
+static int gw_proc_sockaddr_init(server *srv, gw_host *host, gw_proc *proc) {
+ sock_addr addr;
+ socklen_t addrlen;
+
+ if (!buffer_string_is_empty(proc->unixsocket)) {
+ if (1 != sock_addr_from_str_hints(srv, &addr, &addrlen,
+ proc->unixsocket->ptr, AF_UNIX, 0)) {
+ errno = EINVAL;
+ return -1;
+ }
+ buffer_copy_string_len(proc->connection_name, CONST_STR_LEN("unix:"));
+ buffer_append_string_buffer(proc->connection_name, proc->unixsocket);
+ }
+ else {
+ /*(note: name resolution here is *blocking* if IP string not supplied)*/
+ if (1 != sock_addr_from_str_hints(srv, &addr, &addrlen,
+ host->host->ptr, 0, proc->port)) {
+ errno = EINVAL;
+ return -1;
+ }
+ else {
+ /* overwrite host->host buffer with IP addr string so that
+ * any further use of gw_host does not block on DNS lookup */
+ sock_addr_inet_ntop_copy_buffer(host->host, &addr);
+ host->family = sock_addr_get_family(&addr);
+ }
+ buffer_copy_string_len(proc->connection_name, CONST_STR_LEN("tcp:"));
+ buffer_append_string_buffer(proc->connection_name, host->host);
+ buffer_append_string_len(proc->connection_name, CONST_STR_LEN(":"));
+ buffer_append_int(proc->connection_name, proc->port);
+ }
+
+ if (NULL != proc->saddr && proc->saddrlen < addrlen) {
+ free(proc->saddr);
+ proc->saddr = NULL;
+ }
+ if (NULL == proc->saddr) {
+ proc->saddr = (struct sockaddr *)malloc(addrlen);
+ force_assert(proc->saddr);
+ }
+ proc->saddrlen = addrlen;
+ memcpy(proc->saddr, &addr, addrlen);
+ return 0;
+}
+
+static int env_add(char_array *env, const char *key, size_t key_len, const char *val, size_t val_len) {
+ char *dst;
+
+ if (!key || !val) return -1;
+
+ dst = malloc(key_len + val_len + 3);
+ force_assert(dst);
+ memcpy(dst, key, key_len);
+ dst[key_len] = '=';
+ memcpy(dst + key_len + 1, val, val_len + 1); /* add the \0 from the value */
+
+ for (size_t i = 0; i < env->used; ++i) {
+ if (0 == strncmp(dst, env->ptr[i], key_len + 1)) {
+ free(env->ptr[i]);
+ env->ptr[i] = dst;
+ return 0;
+ }
+ }
+
+ if (env->size == 0) {
+ env->size = 16;
+ env->ptr = malloc(env->size * sizeof(*env->ptr));
+ force_assert(env->ptr);
+ } else if (env->size == env->used + 1) {
+ env->size += 16;
+ env->ptr = realloc(env->ptr, env->size * sizeof(*env->ptr));
+ force_assert(env->ptr);
+ }
+
+ env->ptr[env->used++] = dst;
+
+ return 0;
+}
+
+static int gw_spawn_connection(server *srv, gw_host *host, gw_proc *proc, int debug) {
+ int gw_fd;
+ int status;
+ struct timeval tv = { 0, 10 * 1000 };
+
+ if (debug) {
+ log_error_write(srv, __FILE__, __LINE__, "sdb",
+ "new proc, socket:", proc->port, proc->unixsocket);
+ }
+
+ gw_fd = fdevent_socket_cloexec(proc->saddr->sa_family, SOCK_STREAM, 0);
+ if (-1 == gw_fd) {
+ log_error_write(srv, __FILE__, __LINE__, "ss",
+ "failed:", strerror(errno));
+ return -1;
+ }
+
+ do {
+ status = connect(gw_fd, proc->saddr, proc->saddrlen);
+ } while (-1 == status && errno == EINTR);
+
+ if (-1 == status && errno != ENOENT
+ && !buffer_string_is_empty(proc->unixsocket)) {
+ log_error_write(srv, __FILE__, __LINE__, "sbss",
+ "unlink", proc->unixsocket,
+ "after connect failed:", strerror(errno));
+ unlink(proc->unixsocket->ptr);
+ }
+
+ close(gw_fd);
+
+ if (-1 == status) {
+ /* server is not up, spawn it */
+ char_array env;
+ size_t i;
+ int dfd = -1;
+
+ /* reopen socket */
+ gw_fd = fdevent_socket_cloexec(proc->saddr->sa_family, SOCK_STREAM, 0);
+ if (-1 == gw_fd) {
+ log_error_write(srv, __FILE__, __LINE__, "ss",
+ "socket failed:", strerror(errno));
+ return -1;
+ }
+
+ if (fdevent_set_so_reuseaddr(gw_fd, 1) < 0) {
+ log_error_write(srv, __FILE__, __LINE__, "ss",
+ "socketsockopt failed:", strerror(errno));
+ close(gw_fd);
+ return -1;
+ }
+
+ /* create socket */
+ if (-1 == bind(gw_fd, proc->saddr, proc->saddrlen)) {
+ log_error_write(srv, __FILE__, __LINE__, "sbs",
+ "bind failed for:",
+ proc->connection_name,
+ strerror(errno));
+ close(gw_fd);
+ return -1;
+ }
+
+ if (-1 == listen(gw_fd, host->listen_backlog)) {
+ log_error_write(srv, __FILE__, __LINE__, "ss",
+ "listen failed:", strerror(errno));
+ close(gw_fd);
+ return -1;
+ }
+
+ {
+ /* create environment */
+ env.ptr = NULL;
+ env.size = 0;
+ env.used = 0;
+
+ /* build clean environment */
+ if (host->bin_env_copy->used) {
+ for (i = 0; i < host->bin_env_copy->used; ++i) {
+ data_string *ds=(data_string *)host->bin_env_copy->data[i];
+ char *ge;
+
+ if (NULL != (ge = getenv(ds->value->ptr))) {
+ env_add(&env, CONST_BUF_LEN(ds->value), ge, strlen(ge));
+ }
+ }
+ } else {
+ char ** const e = fdevent_environ();
+ for (i = 0; e[i]; ++i) {
+ char *eq;
+
+ if (NULL != (eq = strchr(e[i], '='))) {
+ env_add(&env, e[i], eq - e[i], eq+1, strlen(eq+1));
+ }
+ }
+ }
+
+ /* create environment */
+ for (i = 0; i < host->bin_env->used; ++i) {
+ data_string *ds = (data_string *)host->bin_env->data[i];
+
+ env_add(&env, CONST_BUF_LEN(ds->key), CONST_BUF_LEN(ds->value));
+ }
+
+ for (i = 0; i < env.used; ++i) {
+ /* search for PHP_FCGI_CHILDREN */
+ if (0 == strncmp(env.ptr[i], "PHP_FCGI_CHILDREN=",
+ sizeof("PHP_FCGI_CHILDREN=")-1)) {
+ break;
+ }
+ }
+
+ /* not found, add a default */
+ if (i == env.used) {
+ env_add(&env, CONST_STR_LEN("PHP_FCGI_CHILDREN"),
+ CONST_STR_LEN("1"));
+ }
+
+ env.ptr[env.used] = NULL;
+ }
+
+ dfd = fdevent_open_dirname(host->args.ptr[0]);
+ if (-1 == dfd) {
+ log_error_write(srv, __FILE__, __LINE__, "sss",
+ "open dirname failed:", strerror(errno),
+ host->args.ptr[0]);
+ }
+
+ /*(FCGI_LISTENSOCK_FILENO == STDIN_FILENO == 0)*/
+ proc->pid = (dfd >= 0)
+ ? fdevent_fork_execve(host->args.ptr[0], host->args.ptr,
+ env.ptr, gw_fd, -1, -1, dfd)
+ : -1;
+
+ for (i = 0; i < env.used; ++i) free(env.ptr[i]);
+ free(env.ptr);
+ if (-1 != dfd) close(dfd);
+ close(gw_fd);
+
+ if (-1 == proc->pid) {
+ log_error_write(srv, __FILE__, __LINE__, "sb",
+ "gw-backend failed to start:", host->bin_path);
+ proc->pid = 0;
+ proc->disabled_until = srv->cur_ts;
+ return -1;
+ }
+
+ /* register process */
+ proc->last_used = srv->cur_ts;
+ proc->is_local = 1;
+
+ /* wait */
+ select(0, NULL, NULL, NULL, &tv);
+
+ if (0 != gw_proc_waitpid(srv, host, proc)) {
+ log_error_write(srv, __FILE__, __LINE__, "sb",
+ "gw-backend failed to start:", host->bin_path);
+ log_error_write(srv, __FILE__, __LINE__, "s",
+ "If you're trying to run your app as a FastCGI backend, make "
+ "sure you're using the FastCGI-enabled version. If this is PHP "
+ "on Gentoo, add 'fastcgi' to the USE flags. If this is PHP, try "
+ "removing the bytecode caches for now and try again.");
+ return -1;
+ }
+ } else {
+ proc->is_local = 0;
+ proc->pid = 0;
+
+ if (debug) {
+ log_error_write(srv, __FILE__, __LINE__, "sb",
+ "(debug) socket is already used; won't spawn:",
+ proc->connection_name);
+ }
+ }
+
+ gw_proc_set_state(host, proc, PROC_STATE_RUNNING);
+ return 0;
+}
+
+static void gw_proc_spawn(server *srv, gw_host *host, int debug) {
+ gw_proc *proc;
+ for (proc = host->unused_procs; proc; proc = proc->next) {
+ /* (proc->pid <= 0 indicates PROC_STATE_DIED, not PROC_STATE_KILLED) */
+ if (proc->pid > 0) continue;
+ /* (do not attempt to spawn another proc if a proc just exited) */
+ if (proc->disabled_until >= srv->cur_ts) return;
+ break;
+ }
+ if (proc) {
+ if (proc == host->unused_procs)
+ host->unused_procs = proc->next;
+ else
+ proc->prev->next = proc->next;
+
+ if (proc->next) {
+ proc->next->prev = proc->prev;
+ proc->next = NULL;
+ }
+
+ proc->prev = NULL;
+ } else {
+ proc = gw_proc_init();
+ proc->id = host->max_id++;
+ }
+
+ ++host->num_procs;
+
+ if (buffer_string_is_empty(host->unixsocket)) {
+ proc->port = host->port + proc->id;
+ } else {
+ buffer_copy_buffer(proc->unixsocket, host->unixsocket);
+ buffer_append_string_len(proc->unixsocket, CONST_STR_LEN("-"));
+ buffer_append_int(proc->unixsocket, proc->id);
+ }
+
+ if (0 != gw_proc_sockaddr_init(srv, host, proc)) {
+ /*(should not happen if host->host validated at startup,
+ * and translated from name to IP address at startup)*/
+ log_error_write(srv, __FILE__, __LINE__, "s",
+ "ERROR: spawning backend failed.");
+ --host->num_procs;
+ if (proc->id == host->max_id-1) --host->max_id;
+ gw_proc_free(proc);
+ } else if (gw_spawn_connection(srv, host, proc, debug)) {
+ log_error_write(srv, __FILE__, __LINE__, "s",
+ "ERROR: spawning backend failed.");
+ proc->next = host->unused_procs;
+ if (host->unused_procs)
+ host->unused_procs->prev = proc;
+ host->unused_procs = proc;
+ } else {
+ proc->next = host->first;
+ if (host->first)
+ host->first->prev = proc;
+ host->first = proc;
+ }
+}
+
+static void gw_proc_kill(server *srv, gw_host *host, gw_proc *proc) {
+ UNUSED(srv);
+ if (proc->next) proc->next->prev = proc->prev;
+ if (proc->prev) proc->prev->next = proc->next;
+
+ if (proc->prev == NULL) host->first = proc->next;
+
+ proc->prev = NULL;
+ proc->next = host->unused_procs;
+ proc->disabled_until = 0;
+
+ if (host->unused_procs)
+ host->unused_procs->prev = proc;
+ host->unused_procs = proc;
+
+ kill(proc->pid, host->kill_signal);
+
+ gw_proc_set_state(host, proc, PROC_STATE_KILLED);
+
+ --host->num_procs;
+}
+
+static gw_host * unixsocket_is_dup(gw_plugin_data *p, size_t used, buffer *unixsocket) {
+ for (size_t i = 0; i < used; ++i) {
+ gw_exts *exts = p->config_storage[i]->exts;
+ if (NULL == exts) continue;
+ for (size_t j = 0; j < exts->used; ++j) {
+ gw_extension *ex = exts->exts[j];
+ for (size_t n = 0; n < ex->used; ++n) {
+ gw_host *host = ex->hosts[n];
+ if (!buffer_string_is_empty(host->unixsocket)
+ && buffer_is_equal(host->unixsocket, unixsocket)
+ && !buffer_string_is_empty(host->bin_path))
+ return host;
+ }
+ }
+ }
+
+ return NULL;
+}
+
+static int parse_binpath(char_array *env, buffer *b) {
+ char *start = b->ptr;
+ char c;
+ /* search for spaces */
+ for (size_t i = 0; i < buffer_string_length(b); ++i) {
+ switch(b->ptr[i]) {
+ case ' ':
+ case '\t':
+ /* a WS, stop here and copy the argument */
+
+ if (env->size == 0) {
+ env->size = 16;
+ env->ptr = malloc(env->size * sizeof(*env->ptr));
+ } else if (env->size == env->used) {
+ env->size += 16;
+ env->ptr = realloc(env->ptr, env->size * sizeof(*env->ptr));
+ }
+
+ c = b->ptr[i];
+ b->ptr[i] = '\0';
+ env->ptr[env->used++] = strdup(start);
+ b->ptr[i] = c;
+
+ start = b->ptr + i + 1;
+ break;
+ default:
+ break;
+ }
+ }
+
+ if (env->size == 0) {
+ env->size = 16;
+ env->ptr = malloc(env->size * sizeof(*env->ptr));
+ } else if (env->size == env->used) { /*need one extra for terminating NULL*/
+ env->size += 16;
+ env->ptr = realloc(env->ptr, env->size * sizeof(*env->ptr));
+ }
+
+ /* the rest */
+ env->ptr[env->used++] = strdup(start);
+
+ if (env->size == 0) {
+ env->size = 16;
+ env->ptr = malloc(env->size * sizeof(*env->ptr));
+ } else if (env->size == env->used) { /*need one extra for terminating NULL*/
+ env->size += 16;
+ env->ptr = realloc(env->ptr, env->size * sizeof(*env->ptr));
+ }
+
+ /* terminate */
+ env->ptr[env->used++] = NULL;
+
+ return 0;
+}
+
+enum {
+ GW_BALANCE_LEAST_CONNECTION,
+ GW_BALANCE_RR,
+ GW_BALANCE_HASH,
+ GW_BALANCE_STICKY
+};
+
+static gw_host * gw_host_get(server *srv, connection *con, gw_extension *extension, int balance, int debug) {
+ gw_host *host;
+ unsigned long last_max = ULONG_MAX;
+ int max_usage = INT_MAX;
+ int ndx = -1;
+ size_t k;
+
+ if (extension->used <= 1) {
+ if (1 == extension->used && extension->hosts[0]->active_procs > 0) {
+ ndx = 0;
+ }
+ } else switch(balance) {
+ case GW_BALANCE_HASH:
+ /* hash balancing */
+
+ if (debug) {
+ log_error_write(srv, __FILE__, __LINE__, "sd",
+ "proxy - used hash balancing, hosts:",
+ extension->used);
+ }
+
+ for (k = 0, ndx = -1, last_max = ULONG_MAX; k < extension->used; ++k) {
+ unsigned long cur_max;
+ host = extension->hosts[k];
+ if (0 == host->active_procs) continue;
+
+ cur_max = generate_crc32c(CONST_BUF_LEN(con->uri.path))
+ + generate_crc32c(CONST_BUF_LEN(host->host)) /* cachable */
+ + generate_crc32c(CONST_BUF_LEN(con->uri.authority));
+
+ if (debug) {
+ log_error_write(srv, __FILE__, __LINE__, "sbbbd",
+ "proxy - election:", con->uri.path,
+ host->host, con->uri.authority, cur_max);
+ }
+
+ if (last_max < cur_max || last_max == ULONG_MAX) {
+ last_max = cur_max;
+ ndx = k;
+ }
+ }
+
+ break;
+ case GW_BALANCE_LEAST_CONNECTION:
+ /* fair balancing */
+ if (debug) {
+ log_error_write(srv, __FILE__, __LINE__, "s",
+ "proxy - used least connection");
+ }
+
+ for (k = 0, ndx = -1, max_usage = INT_MAX; k < extension->used; ++k) {
+ host = extension->hosts[k];
+ if (0 == host->active_procs) continue;
+
+ if (host->load < max_usage) {
+ max_usage = host->load;
+ ndx = k;
+ }
+ }
+
+ break;
+ case GW_BALANCE_RR:
+ /* round robin */
+ if (debug) {
+ log_error_write(srv, __FILE__, __LINE__, "s",
+ "proxy - used round-robin balancing");
+ }
+
+ /* just to be sure */
+ force_assert(extension->used < INT_MAX);
+
+ host = extension->hosts[0];
+
+ /* Use last_used_ndx from first host in list */
+ k = extension->last_used_ndx;
+ ndx = k + 1; /* use next host after the last one */
+ if (ndx < 0) ndx = 0;
+
+ /* Search first active host after last_used_ndx */
+ while (ndx < (int) extension->used
+ && 0 == (host = extension->hosts[ndx])->active_procs) ++ndx;
+
+ if (ndx >= (int) extension->used) {
+ /* didn't find a higher id, wrap to the start */
+ for (ndx = 0; ndx <= (int) k; ++ndx) {
+ host = extension->hosts[ndx];
+ if (0 != host->active_procs) break;
+ }
+
+ /* No active host found */
+ if (0 == host->active_procs) ndx = -1;
+ }
+
+ /* Save new index for next round */
+ extension->last_used_ndx = ndx;
+
+ break;
+ case GW_BALANCE_STICKY:
+ /* source sticky balancing */
+
+ if (debug) {
+ log_error_write(srv, __FILE__, __LINE__, "sd",
+ "proxy - used sticky balancing, hosts:",
+ extension->used);
+ }
+
+ for (k = 0, ndx = -1, last_max = ULONG_MAX; k < extension->used; ++k) {
+ unsigned long cur_max;
+ host = extension->hosts[k];
+
+ if (0 == host->active_procs) continue;
+
+ cur_max = generate_crc32c(CONST_BUF_LEN(con->dst_addr_buf))
+ + generate_crc32c(CONST_BUF_LEN(host->host))
+ + host->port;
+
+ if (debug) {
+ log_error_write(srv, __FILE__, __LINE__, "sbbdd",
+ "proxy - election:", con->dst_addr_buf,
+ host->host, host->port, cur_max);
+ }
+
+ if (last_max < cur_max || last_max == ULONG_MAX) {
+ last_max = cur_max;
+ ndx = k;
+ }
+ }
+
+ break;
+ default:
+ break;
+ }
+
+ if (-1 != ndx) {
+ /* found a server */
+ host = extension->hosts[ndx];
+
+ if (debug) {
+ log_error_write(srv, __FILE__, __LINE__, "sbd",
+ "gw - found a host", host->host, host->port);
+ }
+
+ return host;
+ } else if (0 == srv->srvconf.max_worker) {
+ /* special-case adaptive spawning and 0 == host->min_procs */
+ for (k = 0; k < extension->used; ++k) {
+ host = extension->hosts[k];
+ if (0 == host->min_procs && 0 == host->num_procs
+ && !buffer_string_is_empty(host->bin_path)) {
+ gw_proc_spawn(srv, host, debug);
+ if (host->num_procs) return host;
+ }
+ }
+ }
+
+ /* all hosts are down */
+ /* sorry, we don't have a server alive for this ext */
+ con->http_status = 503; /* Service Unavailable */
+ con->mode = DIRECT;
+
+ /* only send the 'no handler' once */
+ if (!extension->note_is_sent) {
+ extension->note_is_sent = 1;
+ log_error_write(srv, __FILE__, __LINE__, "sBSbsbs",
+ "all handlers for", con->uri.path, "?",
+ con->uri.query, "on", extension->key, "are down.");
+ }
+
+ return NULL;
+}
+
+static int gw_establish_connection(server *srv, gw_host *host, gw_proc *proc, pid_t pid, int gw_fd, int debug) {
+ if (-1 == connect(gw_fd, proc->saddr, proc->saddrlen)) {
+ if (errno == EINPROGRESS ||
+ errno == EALREADY ||
+ errno == EINTR) {
+ if (debug > 2) {
+ log_error_write(srv, __FILE__, __LINE__, "sb",
+ "connect delayed; will continue later:",
+ proc->connection_name);
+ }
+
+ return 1;
+ } else {
+ gw_proc_connect_error(srv, host, proc, pid, errno, debug);
+ return -1;
+ }
+ }
+
+ if (debug > 1) {
+ log_error_write(srv, __FILE__, __LINE__, "sd",
+ "connect succeeded: ", gw_fd);
+ }
+
+ return 0;
+}
+
+static void gw_restart_dead_procs(server *srv, gw_host *host, int debug, int trigger) {
+ for (gw_proc *proc = host->first; proc; proc = proc->next) {
+ if (debug > 2) {
+ log_error_write(srv, __FILE__, __LINE__, "sbdddd",
+ "proc:", proc->connection_name, proc->state,
+ proc->is_local, proc->load, proc->pid);
+ }
+
+ switch (proc->state) {
+ case PROC_STATE_RUNNING:
+ break;
+ case PROC_STATE_OVERLOADED:
+ gw_proc_check_enable(srv, host, proc);
+ break;
+ case PROC_STATE_KILLED:
+ if (trigger && ++proc->disabled_until > 4) {
+ int sig = (proc->disabled_until <= 8)
+ ? host->kill_signal
+ : proc->disabled_until <= 16 ? SIGTERM : SIGKILL;
+ kill(proc->pid, sig);
+ }
+ break;
+ case PROC_STATE_DIED_WAIT_FOR_PID:
+ /*(state should not happen in workers if server.max-worker > 0)*/
+ /*(if PROC_STATE_DIED_WAIT_FOR_PID is used in future, might want
+ * to save proc->disabled_until before gw_proc_waitpid() since
+ * gw_proc_waitpid will set proc->disabled_until to srv->cur_ts,
+ * and so process will not be restarted below until one sec later)*/
+ if (0 == gw_proc_waitpid(srv, host, proc)) {
+ gw_proc_check_enable(srv, host, proc);
+ }
+
+ if (proc->state != PROC_STATE_DIED) break;
+ /* fall through *//*(we have a dead proc now)*/
+
+ case PROC_STATE_DIED:
+ /* local procs get restarted by us,
+ * remote ones hopefully by the admin */
+
+ if (!buffer_string_is_empty(host->bin_path)) {
+ /* we still have connections bound to this proc,
+ * let them terminate first */
+ if (proc->load != 0) break;
+
+ /* avoid spinning if child exits too quickly */
+ if (proc->disabled_until >= srv->cur_ts) break;
+
+ /* restart the child */
+
+ if (debug) {
+ log_error_write(srv, __FILE__, __LINE__, "ssbsdsd",
+ "--- gw spawning",
+ "\n\tsocket", proc->connection_name,
+ "\n\tcurrent:", 1, "/", host->max_procs);
+ }
+
+ if (gw_spawn_connection(srv, host, proc, debug)) {
+ log_error_write(srv, __FILE__, __LINE__, "s",
+ "ERROR: spawning gw failed.");
+ }
+ } else {
+ gw_proc_check_enable(srv, host, proc);
+ }
+ break;
+ }
+ }
+}
+
+
+
+
+#include "base.h"
+#include "connections.h"
+#include "joblist.h"
+#include "response.h"
+
+
+/* ok, we need a prototype */
+static handler_t gw_handle_fdevent(server *srv, void *ctx, int revents);
+
+
+static gw_handler_ctx * handler_ctx_init(size_t sz) {
+ gw_handler_ctx *hctx = calloc(1, 0 == sz ? sizeof(*hctx) : sz);
+ force_assert(hctx);
+
+ hctx->fde_ndx = -1;
+
+ /*hctx->response = chunk_buffer_acquire();*//*(allocated when needed)*/
+
+ hctx->request_id = 0;
+ hctx->gw_mode = GW_RESPONDER;
+ hctx->state = GW_STATE_INIT;
+ hctx->proc = NULL;
+
+ hctx->fd = -1;
+
+ hctx->reconnects = 0;
+ hctx->send_content_body = 1;
+
+ /*hctx->rb = chunkqueue_init();*//*(allocated when needed)*/
+ hctx->wb = chunkqueue_init();
+ hctx->wb_reqlen = 0;
+
+ return hctx;
+}
+
+static void handler_ctx_free(gw_handler_ctx *hctx) {
+ /* caller MUST have called gw_backend_close(srv, hctx) if necessary */
+ if (hctx->handler_ctx_free) hctx->handler_ctx_free(hctx);
+ chunk_buffer_release(hctx->response);
+
+ chunkqueue_free(hctx->rb);
+ chunkqueue_free(hctx->wb);
+
+ free(hctx);
+}
+
+static void handler_ctx_clear(gw_handler_ctx *hctx) {
+ /* caller MUST have called gw_backend_close(srv, hctx) if necessary */
+
+ hctx->proc = NULL;
+ hctx->host = NULL;
+ hctx->ext = NULL;
+ /*hctx->ext_auth is intentionally preserved to flag prior authorizer*/
+
+ hctx->gw_mode = GW_RESPONDER;
+ hctx->state = GW_STATE_INIT;
+ /*hctx->state_timestamp = 0;*//*(unused; left as-is)*/
+
+ if (hctx->rb) chunkqueue_reset(hctx->rb);
+ if (hctx->wb) chunkqueue_reset(hctx->wb);
+ hctx->wb_reqlen = 0;
+
+ if (hctx->response) buffer_clear(hctx->response);
+
+ hctx->fd = -1;
+ hctx->fde_ndx = -1;
+ hctx->reconnects = 0;
+ hctx->request_id = 0;
+ hctx->send_content_body = 1;
+
+ /*plugin_config conf;*//*(no need to reset for same request)*/
+
+ /*hctx->remote_conn = NULL;*//*(no need to reset for same request)*/
+ /*hctx->plugin_data = NULL;*//*(no need to reset for same request)*/
+}
+
+
+void * gw_init(void) {
+ return calloc(1, sizeof(gw_plugin_data));
+}
+
+
+void gw_plugin_config_free(gw_plugin_config *s) {
+ gw_exts *exts = s->exts;
+ if (exts) {
+ for (size_t j = 0; j < exts->used; ++j) {
+ gw_extension *ex = exts->exts[j];
+ for (size_t n = 0; n < ex->used; ++n) {
+ gw_proc *proc;
+ gw_host *host = ex->hosts[n];
+
+ for (proc = host->first; proc; proc = proc->next) {
+ if (proc->pid > 0) {
+ kill(proc->pid, host->kill_signal);
+ }
+
+ if (proc->is_local &&
+ !buffer_string_is_empty(proc->unixsocket)) {
+ unlink(proc->unixsocket->ptr);
+ }
+ }
+
+ for (proc = host->unused_procs; proc; proc = proc->next) {
+ if (proc->pid > 0) {
+ kill(proc->pid, host->kill_signal);
+ }
+ if (proc->is_local &&
+ !buffer_string_is_empty(proc->unixsocket)) {
+ unlink(proc->unixsocket->ptr);
+ }
+ }
+ }
+ }
+
+ gw_extensions_free(s->exts);
+ gw_extensions_free(s->exts_auth);
+ gw_extensions_free(s->exts_resp);
+ }
+ array_free(s->ext_mapping);
+ free(s);
+}
+
+handler_t gw_free(server *srv, void *p_d) {
+ gw_plugin_data *p = p_d;
+ if (p->config_storage) {
+ for (size_t i = 0; i < srv->config_context->used; ++i) {
+ gw_plugin_config *s = p->config_storage[i];
+ if (NULL == s) continue;
+ gw_plugin_config_free(s);
+ }
+ free(p->config_storage);
+ }
+ free(p);
+ return HANDLER_GO_ON;
+}
+
+int gw_set_defaults_backend(server *srv, gw_plugin_data *p, data_unset *du, size_t i, int sh_exec) {
+ /* per-module plugin_config MUST have common "base class" gw_plugin_config*/
+ /* per-module plugin_data MUST have pointer-compatible common "base class"
+ * with gw_plugin_data (stemming from gw_plugin_config compatibility) */
+
+ data_array *da = (data_array *)du;
+ gw_plugin_config *s = p->config_storage[i];
+ buffer *gw_mode;
+ gw_host *host = NULL;
+
+ if (NULL == da) return 1;
+
+ if (da->type != TYPE_ARRAY || !array_is_kvarray(da->value)) {
+ log_error_write(srv, __FILE__, __LINE__, "s",
+ "unexpected value for xxxxx.server; expected "
+ "( \"ext\" => ( \"backend-label\" => ( \"key\" => \"value\" )))");
+ return 0;
+ }
+
+ p->srv_pid = srv->pid;
+
+ gw_mode = buffer_init();
+
+ s->exts = gw_extensions_init();
+ s->exts_auth = gw_extensions_init();
+ s->exts_resp = gw_extensions_init();
+ /*s->balance = GW_BALANCE_LEAST_CONNECTION;*//*(default)*/
+
+ /*
+ * gw.server = ( "<ext>" => ( ... ),
+ * "<ext>" => ( ... ) )
+ */
+
+ for (size_t j = 0; j < da->value->used; ++j) {
+ data_array *da_ext = (data_array *)da->value->data[j];
+
+ /*
+ * da_ext->key == name of the extension
+ */
+
+ /*
+ * gw.server = ( "<ext>" =>
+ * ( "<host>" => ( ... ),
+ * "<host>" => ( ... )
+ * ),
+ * "<ext>" => ... )
+ */
+
+ for (size_t n = 0; n < da_ext->value->used; ++n) {
+ data_array *da_host = (data_array *)da_ext->value->data[n];
+
+ config_values_t fcv[] = {
+ { "host", NULL, T_CONFIG_STRING, T_CONFIG_SCOPE_CONNECTION }, /* 0 */
+ { "docroot", NULL, T_CONFIG_STRING, T_CONFIG_SCOPE_CONNECTION }, /* 1 */
+ { "mode", NULL, T_CONFIG_STRING, T_CONFIG_SCOPE_CONNECTION }, /* 2 */
+ { "socket", NULL, T_CONFIG_STRING, T_CONFIG_SCOPE_CONNECTION }, /* 3 */
+ { "bin-path", NULL, T_CONFIG_STRING, T_CONFIG_SCOPE_CONNECTION }, /* 4 */
+
+ { "check-local", NULL, T_CONFIG_BOOLEAN, T_CONFIG_SCOPE_CONNECTION }, /* 5 */
+ { "port", NULL, T_CONFIG_SHORT, T_CONFIG_SCOPE_CONNECTION }, /* 6 */
+ { "min-procs", NULL, T_CONFIG_SHORT, T_CONFIG_SCOPE_CONNECTION }, /* 7 */
+ { "max-procs", NULL, T_CONFIG_SHORT, T_CONFIG_SCOPE_CONNECTION }, /* 8 */
+ { "max-load-per-proc", NULL, T_CONFIG_SHORT, T_CONFIG_SCOPE_CONNECTION }, /* 9 */
+ { "idle-timeout", NULL, T_CONFIG_SHORT, T_CONFIG_SCOPE_CONNECTION }, /* 10 */
+ { "disable-time", NULL, T_CONFIG_SHORT, T_CONFIG_SCOPE_CONNECTION }, /* 11 */
+
+ { "bin-environment", NULL, T_CONFIG_ARRAY, T_CONFIG_SCOPE_CONNECTION }, /* 12 */
+ { "bin-copy-environment", NULL, T_CONFIG_ARRAY, T_CONFIG_SCOPE_CONNECTION }, /* 13 */
+
+ { "broken-scriptfilename", NULL, T_CONFIG_BOOLEAN, T_CONFIG_SCOPE_CONNECTION }, /* 14 */
+ { "allow-x-send-file", NULL, T_CONFIG_BOOLEAN, T_CONFIG_SCOPE_CONNECTION }, /* 15 */
+ { "strip-request-uri", NULL, T_CONFIG_STRING, T_CONFIG_SCOPE_CONNECTION }, /* 16 */
+ { "kill-signal", NULL, T_CONFIG_SHORT, T_CONFIG_SCOPE_CONNECTION }, /* 17 */
+ { "fix-root-scriptname", NULL, T_CONFIG_BOOLEAN, T_CONFIG_SCOPE_CONNECTION }, /* 18 */
+ { "listen-backlog", NULL, T_CONFIG_INT, T_CONFIG_SCOPE_CONNECTION }, /* 19 */
+ { "x-sendfile", NULL, T_CONFIG_BOOLEAN, T_CONFIG_SCOPE_CONNECTION }, /* 20 */
+ { "x-sendfile-docroot",NULL, T_CONFIG_ARRAY, T_CONFIG_SCOPE_CONNECTION }, /* 21 */
+ { "tcp-fin-propagate", NULL, T_CONFIG_BOOLEAN, T_CONFIG_SCOPE_CONNECTION }, /* 22 */
+
+ { NULL, NULL, T_CONFIG_UNSET, T_CONFIG_SCOPE_UNSET }
+ };
+ unsigned short host_mode = GW_RESPONDER;
+
+ if (da_host->type != TYPE_ARRAY || !array_is_kvany(da_host->value)){
+ log_error_write(srv, __FILE__, __LINE__, "SBS",
+ "unexpected value for gw.server near [",
+ da_host->key, "](string); expected ( \"ext\" => ( \"backend-label\" => ( \"key\" => \"value\" )))");
+
+ goto error;
+ }
+
+ host = gw_host_init();
+ buffer_clear(gw_mode);
+
+ buffer_copy_buffer(host->id, da_host->key);
+
+ host->check_local = 1;
+ host->min_procs = 4;
+ host->max_procs = 4;
+ host->max_load_per_proc = 1;
+ host->idle_timeout = 60;
+ host->disable_time = 1;
+ host->break_scriptfilename_for_php = 0;
+ host->kill_signal = SIGTERM;
+ host->fix_root_path_name = 0;
+ host->listen_backlog = 1024;
+ host->xsendfile_allow = 0;
+ host->refcount = 0;
+
+ fcv[0].destination = host->host;
+ fcv[1].destination = host->docroot;
+ fcv[2].destination = gw_mode;
+ fcv[3].destination = host->unixsocket;
+ fcv[4].destination = host->bin_path;
+
+ fcv[5].destination = &(host->check_local);
+ fcv[6].destination = &(host->port);
+ fcv[7].destination = &(host->min_procs);
+ fcv[8].destination = &(host->max_procs);
+ fcv[9].destination = &(host->max_load_per_proc);
+ fcv[10].destination = &(host->idle_timeout);
+ fcv[11].destination = &(host->disable_time);
+
+ fcv[12].destination = host->bin_env;
+ fcv[13].destination = host->bin_env_copy;
+ fcv[14].destination = &(host->break_scriptfilename_for_php);
+ fcv[15].destination = &(host->xsendfile_allow);
+ fcv[16].destination = host->strip_request_uri;
+ fcv[17].destination = &(host->kill_signal);
+ fcv[18].destination = &(host->fix_root_path_name);
+ fcv[19].destination = &(host->listen_backlog);
+ fcv[20].destination = &(host->xsendfile_allow);
+ fcv[21].destination = host->xsendfile_docroot;
+ fcv[22].destination = &(host->tcp_fin_propagate);
+
+ if (0 != config_insert_values_internal(srv, da_host->value, fcv, T_CONFIG_SCOPE_CONNECTION)) {
+ goto error;
+ }
+
+ for (size_t m = 0; m < da_host->value->used; ++m) {
+ if (NULL != strchr(da_host->value->data[m]->key->ptr, '_')) {
+ log_error_write(srv, __FILE__, __LINE__, "sb",
+ "incorrect directive contains underscore ('_') instead of dash ('-'):",
+ da_host->value->data[m]->key);
+ }
+ }
+
+ if ((!buffer_string_is_empty(host->host) || host->port)
+ && !buffer_string_is_empty(host->unixsocket)) {
+ log_error_write(srv, __FILE__, __LINE__, "sbsbsbs",
+ "either host/port or socket have to be set in:",
+ da->key, "= (",
+ da_ext->key, " => (",
+ da_host->key, " ( ...");
+
+ goto error;
+ }
+
+ if (!buffer_string_is_empty(host->host) && *host->host->ptr == '/'
+ && buffer_string_is_empty(host->unixsocket)) {
+ buffer_copy_buffer(host->unixsocket, host->host);
+ }
+
+ if (!buffer_string_is_empty(host->unixsocket)) {
+ /* unix domain socket */
+ struct sockaddr_un un;
+
+ if (buffer_string_length(host->unixsocket) + 1 > sizeof(un.sun_path) - 2) {
+ log_error_write(srv, __FILE__, __LINE__, "sbsbsbs",
+ "unixsocket is too long in:",
+ da->key, "= (",
+ da_ext->key, " => (",
+ da_host->key, " ( ...");
+
+ goto error;
+ }
+
+ if (!buffer_string_is_empty(host->bin_path)) {
+ gw_host *duplicate = unixsocket_is_dup(p, i+1, host->unixsocket);
+ if (NULL != duplicate) {
+ if (!buffer_is_equal(host->bin_path, duplicate->bin_path)) {
+ log_error_write(srv, __FILE__, __LINE__, "sb",
+ "duplicate unixsocket path:",
+ host->unixsocket);
+ goto error;
+ }
+ gw_host_free(host);
+ host = duplicate;
+ ++host->refcount;
+ }
+ }
+
+ host->family = AF_UNIX;
+ } else {
+ /* tcp/ip */
+
+ if (buffer_string_is_empty(host->host) &&
+ buffer_string_is_empty(host->bin_path)) {
+ log_error_write(srv, __FILE__, __LINE__, "sbsbsbs",
+ "host or binpath have to be set in:",
+ da->key, "= (",
+ da_ext->key, " => (",
+ da_host->key, " ( ...");
+
+ goto error;
+ } else if (0 == host->port) {
+ host->port = 80;
+ }
+
+ if (buffer_string_is_empty(host->host)) {
+ buffer_copy_string_len(host->host,
+ CONST_STR_LEN("127.0.0.1"));
+ }
+
+ host->family = (NULL != strchr(host->host->ptr, ':'))
+ ? AF_INET6
+ : AF_INET;
+ }
+
+ if (host->refcount) {
+ /* already init'd; skip spawning */
+ } else if (!buffer_string_is_empty(host->bin_path)) {
+ /* a local socket + self spawning */
+ struct stat st;
+ parse_binpath(&host->args, host->bin_path);
+ if (0 != stat(host->args.ptr[0], &st) || !S_ISREG(st.st_mode)
+ || !(st.st_mode & (S_IXUSR | S_IXGRP | S_IXOTH))) {
+ log_error_write(srv, __FILE__, __LINE__, "SSs",
+ "invalid \"bin-path\" => \"", host->bin_path->ptr,
+ "\" (check that file exists, is regular file, "
+ "and is executable by lighttpd)");
+ }
+
+ if (sh_exec) {
+ /*(preserve prior behavior for SCGI exec of command)*/
+ /*(admin should really prefer to put
+ * any complex command into a script)*/
+ for (size_t m = 0; m < host->args.used; ++m)
+ free(host->args.ptr[m]);
+ free(host->args.ptr);
+
+ host->args.ptr = calloc(4, sizeof(char *));
+ force_assert(host->args.ptr);
+ host->args.used = 3;
+ host->args.size = 4;
+ host->args.ptr[0] = malloc(sizeof("/bin/sh"));
+ force_assert(host->args.ptr[0]);
+ memcpy(host->args.ptr[0], "/bin/sh", sizeof("/bin/sh"));
+ host->args.ptr[1] = malloc(sizeof("-c"));
+ force_assert(host->args.ptr[1]);
+ memcpy(host->args.ptr[1], "-c", sizeof("-c"));
+ host->args.ptr[2] =
+ malloc(sizeof("exec ")-1
+ + buffer_string_length(host->bin_path) + 1);
+ force_assert(host->args.ptr[2]);
+ memcpy(host->args.ptr[2], "exec ", sizeof("exec ")-1);
+ memcpy(host->args.ptr[2]+sizeof("exec ")-1,
+ host->bin_path->ptr,
+ buffer_string_length(host->bin_path)+1);
+ host->args.ptr[3] = NULL;
+ }
+
+ if (host->min_procs > host->max_procs)
+ host->min_procs = host->max_procs;
+ if (host->min_procs!= host->max_procs
+ && 0 != srv->srvconf.max_worker) {
+ host->min_procs = host->max_procs;
+ log_error_write(srv, __FILE__, __LINE__, "s",
+ "adaptive backend spawning disabled "
+ "(server.max_worker is non-zero)");
+ }
+ if (host->max_load_per_proc < 1)
+ host->max_load_per_proc = 0;
+
+ if (s->debug) {
+ log_error_write(srv, __FILE__, __LINE__, "ssbsdsbsdsd",
+ "--- gw spawning local",
+ "\n\tproc:", host->bin_path,
+ "\n\tport:", host->port,
+ "\n\tsocket", host->unixsocket,
+ "\n\tmin-procs:", host->min_procs,
+ "\n\tmax-procs:", host->max_procs);
+ }
+
+ for (size_t pno = 0; pno < host->min_procs; ++pno) {
+ gw_proc *proc = gw_proc_init();
+ proc->id = host->num_procs++;
+ host->max_id++;
+
+ if (buffer_string_is_empty(host->unixsocket)) {
+ proc->port = host->port + pno;
+ } else {
+ buffer_copy_buffer(proc->unixsocket, host->unixsocket);
+ buffer_append_string_len(proc->unixsocket,
+ CONST_STR_LEN("-"));
+ buffer_append_int(proc->unixsocket, pno);
+ }
+
+ if (s->debug) {
+ log_error_write(srv, __FILE__, __LINE__, "ssdsbsdsd",
+ "--- gw spawning",
+ "\n\tport:", host->port,
+ "\n\tsocket", host->unixsocket,
+ "\n\tcurrent:", pno, "/", host->max_procs);
+ }
+
+ if (0 != gw_proc_sockaddr_init(srv, host, proc)) {
+ gw_proc_free(proc);
+ goto error;
+ }
+
+ if (!srv->srvconf.preflight_check
+ && gw_spawn_connection(srv, host, proc, s->debug)) {
+ log_error_write(srv, __FILE__, __LINE__, "s",
+ "[ERROR]: spawning gw failed.");
+ gw_proc_free(proc);
+ goto error;
+ }
+
+ gw_status_init(srv, host, proc);
+
+ proc->next = host->first;
+ if (host->first) host->first->prev = proc;
+
+ host->first = proc;
+ }
+ } else {
+ gw_proc *proc;
+
+ proc = gw_proc_init();
+ proc->id = host->num_procs++;
+ host->max_id++;
+ gw_proc_set_state(host, proc, PROC_STATE_RUNNING);
+
+ if (buffer_string_is_empty(host->unixsocket)) {
+ proc->port = host->port;
+ } else {
+ buffer_copy_buffer(proc->unixsocket, host->unixsocket);
+ }
+
+ gw_status_init(srv, host, proc);
+
+ host->first = proc;
+
+ host->min_procs = 1;
+ host->max_procs = 1;
+
+ if (0 != gw_proc_sockaddr_init(srv, host, proc)) goto error;
+ }
+
+ if (!buffer_string_is_empty(gw_mode)) {
+ if (strcmp(gw_mode->ptr, "responder") == 0) {
+ host_mode = GW_RESPONDER;
+ } else if (strcmp(gw_mode->ptr, "authorizer") == 0) {
+ host_mode = GW_AUTHORIZER;
+ } else {
+ log_error_write(srv, __FILE__, __LINE__, "sbs",
+ "WARNING: unknown gw mode:",
+ gw_mode,"(ignored, mode set to responder)");
+ }
+ }
+
+ if (host->xsendfile_docroot->used) {
+ size_t k;
+ for (k = 0; k < host->xsendfile_docroot->used; ++k) {
+ data_string *ds = (data_string *)host->xsendfile_docroot->data[k];
+ if (ds->type != TYPE_STRING) {
+ log_error_write(srv, __FILE__, __LINE__, "s",
+ "unexpected type for x-sendfile-docroot; expected: \"x-sendfile-docroot\" => ( \"/allowed/path\", ... )");
+ goto error;
+ }
+ if (ds->value->ptr[0] != '/') {
+ log_error_write(srv, __FILE__, __LINE__, "SBs",
+ "x-sendfile-docroot paths must begin with '/'; invalid: \"", ds->value, "\"");
+ goto error;
+ }
+ buffer_path_simplify(ds->value, ds->value);
+ buffer_append_slash(ds->value);
+ }
+ }
+
+ /* s->exts is list of exts -> hosts
+ * s->exts now used as combined list
+ * of authorizer and responder hosts (for backend maintenance)
+ * s->exts_auth is list of exts -> authorizer hosts
+ * s->exts_resp is list of exts -> responder hosts
+ * For each path/extension:
+ * there may be an independent GW_AUTHORIZER and GW_RESPONDER
+ * (The GW_AUTHORIZER and GW_RESPONDER could be handled by the same
+ * host, and an admin might want to do that for large uploads,
+ * since GW_AUTHORIZER runs prior to receiving (potentially large)
+ * request body from client and can authorizer or deny request
+ * prior to receiving the full upload)
+ */
+ gw_extension_insert(s->exts, da_ext->key, host);
+
+ if (host_mode == GW_AUTHORIZER) {
+ ++host->refcount;
+ gw_extension_insert(s->exts_auth, da_ext->key, host);
+ } else if (host_mode == GW_RESPONDER) {
+ ++host->refcount;
+ gw_extension_insert(s->exts_resp, da_ext->key, host);
+ } /*(else should have been rejected above)*/
+
+ host = NULL;
+ }
+ }
+
+ buffer_free(gw_mode);
+ return 1;
+
+error:
+ if (NULL != host) gw_host_free(host);
+ buffer_free(gw_mode);
+ return 0;
+}
+
+int gw_set_defaults_balance(server *srv, gw_plugin_config *s, data_unset *du) {
+ buffer *b;
+ if (NULL == du) {
+ b = NULL;
+ } else if (du->type == TYPE_STRING) {
+ b = ((data_string *)du)->value;
+ } else {
+ log_error_write(srv, __FILE__, __LINE__, "s",
+ "unexpected type for xxxxx.balance; expected string");
+ return 0;
+ }
+ if (buffer_string_is_empty(b)) {
+ s->balance = GW_BALANCE_LEAST_CONNECTION;
+ } else if (buffer_is_equal_string(b, CONST_STR_LEN("fair"))) {
+ s->balance = GW_BALANCE_LEAST_CONNECTION;
+ } else if (buffer_is_equal_string(b, CONST_STR_LEN("least-connection"))) {
+ s->balance = GW_BALANCE_LEAST_CONNECTION;
+ } else if (buffer_is_equal_string(b, CONST_STR_LEN("round-robin"))) {
+ s->balance = GW_BALANCE_RR;
+ } else if (buffer_is_equal_string(b, CONST_STR_LEN("hash"))) {
+ s->balance = GW_BALANCE_HASH;
+ } else if (buffer_is_equal_string(b, CONST_STR_LEN("sticky"))) {
+ s->balance = GW_BALANCE_STICKY;
+ } else {
+ log_error_write(srv, __FILE__, __LINE__, "sb",
+ "xxxxx.balance has to be one of: "
+ "least-connection, round-robin, hash, sticky, but not:",
+ b);
+ return 0;
+ }
+ return 1;
+}
+
+static void gw_set_state(server *srv, gw_handler_ctx *hctx, gw_connection_state_t state) {
+ hctx->state = state;
+ hctx->state_timestamp = srv->cur_ts;
+}
+
+
+void gw_set_transparent(server *srv, gw_handler_ctx *hctx) {
+ if (AF_UNIX != hctx->host->family) {
+ if (-1 == fdevent_set_tcp_nodelay(hctx->fd, 1)) {
+ /*(error, but not critical)*/
+ }
+ }
+ hctx->wb_reqlen = -1;
+ gw_set_state(srv, hctx, GW_STATE_WRITE);
+}
+
+
+static void gw_backend_close(server *srv, gw_handler_ctx *hctx) {
+ if (hctx->fd >= 0) {
+ fdevent_event_del(srv->ev, &(hctx->fde_ndx), hctx->fd);
+ /*fdevent_unregister(srv->ev, hctx->fd);*//*(handled below)*/
+ fdevent_sched_close(srv->ev, hctx->fd, 1);
+ hctx->fd = -1;
+ hctx->fde_ndx = -1;
+ }
+
+ if (hctx->host) {
+ if (hctx->proc) {
+ gw_proc_release(srv, hctx->host, hctx->proc, hctx->conf.debug);
+ hctx->proc = NULL;
+ }
+
+ gw_host_reset(srv, hctx->host);
+ hctx->host = NULL;
+ }
+}
+
+static void gw_connection_close(server *srv, gw_handler_ctx *hctx) {
+ gw_plugin_data *p = hctx->plugin_data;
+ connection *con = hctx->remote_conn;
+
+ gw_backend_close(srv, hctx);
+ handler_ctx_free(hctx);
+ con->plugin_ctx[p->id] = NULL;
+
+ if (con->mode == p->id) {
+ http_response_backend_done(srv, con);
+ }
+}
+
+static handler_t gw_reconnect(server *srv, gw_handler_ctx *hctx) {
+ gw_backend_close(srv, hctx);
+
+ hctx->host = gw_host_get(srv, hctx->remote_conn, hctx->ext,
+ hctx->conf.balance, hctx->conf.debug);
+ if (NULL == hctx->host) return HANDLER_FINISHED;
+
+ gw_host_assign(srv, hctx->host);
+ hctx->request_id = 0;
+ hctx->opts.xsendfile_allow = hctx->host->xsendfile_allow;
+ hctx->opts.xsendfile_docroot = hctx->host->xsendfile_docroot;
+ gw_set_state(srv, hctx, GW_STATE_INIT);
+ return HANDLER_COMEBACK;
+}
+
+
+handler_t gw_connection_reset(server *srv, connection *con, void *p_d) {
+ gw_plugin_data *p = p_d;
+ gw_handler_ctx *hctx = con->plugin_ctx[p->id];
+ if (hctx) gw_connection_close(srv, hctx);
+
+ return HANDLER_GO_ON;
+}
+
+
+static void gw_conditional_tcp_fin(server *srv, gw_handler_ctx *hctx) {
+ connection *con = hctx->remote_conn;
+ /*assert(con->conf.stream_request_body & FDEVENT_STREAM_REQUEST_TCP_FIN);*/
+ if (!chunkqueue_is_empty(hctx->wb)) return;
+ if (!hctx->host->tcp_fin_propagate) return;
+ if (hctx->gw_mode == GW_AUTHORIZER) return;
+ if (con->conf.stream_request_body & FDEVENT_STREAM_REQUEST_BACKEND_SHUT_WR)
+ return;
+
+ /* propagate shutdown SHUT_WR to backend if TCP half-close on con->fd */
+ con->conf.stream_request_body |= FDEVENT_STREAM_REQUEST_BACKEND_SHUT_WR;
+ con->conf.stream_request_body &= ~FDEVENT_STREAM_REQUEST_POLLIN;
+ con->is_readable = 0;
+ shutdown(hctx->fd, SHUT_WR);
+ fdevent_event_clr(srv->ev, &hctx->fde_ndx, hctx->fd, FDEVENT_OUT);
+}
+
+static handler_t gw_write_request(server *srv, gw_handler_ctx *hctx) {
+ switch(hctx->state) {
+ case GW_STATE_INIT:
+ /* do we have a running process for this host (max-procs) ? */
+ hctx->proc = NULL;
+
+ for (gw_proc *proc = hctx->host->first; proc; proc = proc->next) {
+ if (proc->state == PROC_STATE_RUNNING) {
+ hctx->proc = proc;
+ break;
+ }
+ }
+
+ /* all children are dead */
+ if (hctx->proc == NULL) {
+ return HANDLER_ERROR;
+ }
+
+ /* check the other procs if they have a lower load */
+ for (gw_proc *proc = hctx->proc->next; proc; proc = proc->next) {
+ if (proc->state != PROC_STATE_RUNNING) continue;
+ if (proc->load < hctx->proc->load) hctx->proc = proc;
+ }
+
+ gw_proc_load_inc(srv, hctx->host, hctx->proc);
+
+ hctx->fd = fdevent_socket_nb_cloexec(hctx->host->family,SOCK_STREAM,0);
+ if (-1 == hctx->fd) {
+ if (errno == EMFILE || errno == EINTR) {
+ log_error_write(srv, __FILE__, __LINE__, "sd",
+ "wait for fd at connection:",
+ hctx->remote_conn->fd);
+ return HANDLER_WAIT_FOR_FD;
+ }
+
+ log_error_write(srv, __FILE__, __LINE__, "ssdd",
+ "socket failed:", strerror(errno),
+ srv->cur_fds, srv->max_fds);
+ return HANDLER_ERROR;
+ }
+
+ srv->cur_fds++;
+
+ fdevent_register(srv->ev, hctx->fd, gw_handle_fdevent, hctx);
+
+ if (hctx->proc->is_local) {
+ hctx->pid = hctx->proc->pid;
+ }
+
+ switch (gw_establish_connection(srv, hctx->host, hctx->proc, hctx->pid,
+ hctx->fd, hctx->conf.debug)) {
+ case 1: /* connection is in progress */
+ fdevent_event_set(srv->ev, &(hctx->fde_ndx), hctx->fd, FDEVENT_OUT);
+ gw_set_state(srv, hctx, GW_STATE_CONNECT_DELAYED);
+ return HANDLER_WAIT_FOR_EVENT;
+ case -1:/* connection error */
+ return HANDLER_ERROR;
+ case 0: /* everything is ok, go on */
+ hctx->reconnects = 0;
+ break;
+ }
+ /* fall through */
+ case GW_STATE_CONNECT_DELAYED:
+ if (hctx->state == GW_STATE_CONNECT_DELAYED) { /*(not GW_STATE_INIT)*/
+ int socket_error = fdevent_connect_status(hctx->fd);
+ if (socket_error != 0) {
+ gw_proc_connect_error(srv, hctx->host, hctx->proc, hctx->pid,
+ socket_error, hctx->conf.debug);
+ return HANDLER_ERROR;
+ }
+ /* go on with preparing the request */
+ }
+
+ gw_proc_connect_success(srv, hctx->host, hctx->proc, hctx->conf.debug);
+
+ gw_set_state(srv, hctx, GW_STATE_PREPARE_WRITE);
+ /* fall through */
+ case GW_STATE_PREPARE_WRITE:
+ /* ok, we have the connection */
+
+ {
+ handler_t rc = hctx->create_env(srv, hctx);
+ if (HANDLER_GO_ON != rc) {
+ if (HANDLER_FINISHED != rc && HANDLER_ERROR != rc)
+ fdevent_event_clr(srv->ev, &(hctx->fde_ndx), hctx->fd,
+ FDEVENT_OUT);
+ return rc;
+ }
+ }
+
+ /*(disable Nagle algorithm if streaming and content-length unknown)*/
+ if (AF_UNIX != hctx->host->family) {
+ connection *con = hctx->remote_conn;
+ if (con->request.content_length < 0) {
+ if (-1 == fdevent_set_tcp_nodelay(hctx->fd, 1)) {
+ /*(error, but not critical)*/
+ }
+ }
+ }
+
+ fdevent_event_add(srv->ev, &hctx->fde_ndx, hctx->fd,
+ FDEVENT_IN | FDEVENT_RDHUP);
+ gw_set_state(srv, hctx, GW_STATE_WRITE);
+ /* fall through */
+ case GW_STATE_WRITE:
+ if (!chunkqueue_is_empty(hctx->wb)) {
+ int ret;
+ #if 0
+ if (hctx->conf.debug > 1) {
+ log_error_write(srv, __FILE__, __LINE__, "sdsx",
+ "send data to backend ( fd =", hctx->fd,
+ "), size =", chunkqueue_length(hctx->wb));
+ }
+ #endif
+ ret = srv->network_backend_write(srv, hctx->fd, hctx->wb,
+ MAX_WRITE_LIMIT);
+
+ chunkqueue_remove_finished_chunks(hctx->wb);
+
+ if (ret < 0) {
+ switch(errno) {
+ case EPIPE:
+ case ENOTCONN:
+ case ECONNRESET:
+ /* the connection got dropped after accept()
+ * we don't care about that --
+ * if you accept() it, you have to handle it.
+ */
+ log_error_write(srv, __FILE__, __LINE__, "ssosb",
+ "connection was dropped after accept() "
+ "(perhaps the gw process died),",
+ "write-offset:", hctx->wb->bytes_out,
+ "socket:", hctx->proc->connection_name);
+ return HANDLER_ERROR;
+ default:
+ log_error_write(srv, __FILE__, __LINE__, "ssd",
+ "write failed:", strerror(errno), errno);
+ return HANDLER_ERROR;
+ }
+ }
+ }
+
+ if (hctx->wb->bytes_out == hctx->wb_reqlen) {
+ fdevent_event_clr(srv->ev, &(hctx->fde_ndx), hctx->fd, FDEVENT_OUT);
+ gw_set_state(srv, hctx, GW_STATE_READ);
+ } else {
+ off_t wblen = hctx->wb->bytes_in - hctx->wb->bytes_out;
+ if ((hctx->wb->bytes_in < hctx->wb_reqlen || hctx->wb_reqlen < 0)
+ && wblen < 65536 - 16384) {
+ connection *con = hctx->remote_conn;
+ /*(con->conf.stream_request_body & FDEVENT_STREAM_REQUEST)*/
+ if (!(con->conf.stream_request_body
+ & FDEVENT_STREAM_REQUEST_POLLIN)) {
+ con->conf.stream_request_body |=
+ FDEVENT_STREAM_REQUEST_POLLIN;
+ con->is_readable = 1;/*trigger optimistic read from client*/
+ }
+ }
+ if (0 == wblen) {
+ fdevent_event_clr(srv->ev,&hctx->fde_ndx,hctx->fd,FDEVENT_OUT);
+ } else {
+ fdevent_event_add(srv->ev,&hctx->fde_ndx,hctx->fd,FDEVENT_OUT);
+ }
+ }
+
+ if (hctx->remote_conn->conf.stream_request_body
+ & FDEVENT_STREAM_REQUEST_TCP_FIN)
+ gw_conditional_tcp_fin(srv, hctx);
+
+ return HANDLER_WAIT_FOR_EVENT;
+ case GW_STATE_READ:
+ /* waiting for a response */
+ return HANDLER_WAIT_FOR_EVENT;
+ default:
+ log_error_write(srv, __FILE__, __LINE__, "s", "(debug) unknown state");
+ return HANDLER_ERROR;
+ }
+}
+
+static handler_t gw_write_error(server *srv, gw_handler_ctx *hctx) {
+ connection *con = hctx->remote_conn;
+ int status = con->http_status;
+
+ if (hctx->state == GW_STATE_INIT ||
+ hctx->state == GW_STATE_CONNECT_DELAYED) {
+
+ /* (optimization to detect backend process exit while processing a
+ * large number of ready events; (this block could be removed)) */
+ if (0 == srv->srvconf.max_worker)
+ gw_restart_dead_procs(srv, hctx->host, hctx->conf.debug, 0);
+
+ /* cleanup this request and let request handler start request again */
+ if (hctx->reconnects++ < 5) return gw_reconnect(srv, hctx);
+ }
+
+ if (hctx->backend_error) hctx->backend_error(hctx);
+ gw_connection_close(srv, hctx);
+ con->http_status = (status == 400) ? 400 : 503;
+ return HANDLER_FINISHED;
+}
+
+static handler_t gw_send_request(server *srv, gw_handler_ctx *hctx) {
+ handler_t rc = gw_write_request(srv, hctx);
+ return (HANDLER_ERROR != rc) ? rc : gw_write_error(srv, hctx);
+}
+
+
+static handler_t gw_recv_response(server *srv, gw_handler_ctx *hctx);
+
+
+handler_t gw_handle_subrequest(server *srv, connection *con, void *p_d) {
+ gw_plugin_data *p = p_d;
+ gw_handler_ctx *hctx = con->plugin_ctx[p->id];
+ if (NULL == hctx) return HANDLER_GO_ON;
+ if (con->mode != p->id) return HANDLER_GO_ON; /* not my job */
+
+ if ((con->conf.stream_response_body & FDEVENT_STREAM_RESPONSE_BUFMIN)
+ && con->file_started) {
+ if (chunkqueue_length(con->write_queue) > 65536 - 4096) {
+ fdevent_event_clr(srv->ev, &(hctx->fde_ndx), hctx->fd, FDEVENT_IN);
+ }
+ else if (!(fdevent_event_get_interest(srv->ev, hctx->fd) & FDEVENT_IN)){
+ /* optimistic read from backend */
+ handler_t rc;
+ rc = gw_recv_response(srv, hctx); /*(might invalidate hctx)*/
+ if (rc != HANDLER_GO_ON) return rc; /*(unless HANDLER_GO_ON)*/
+ fdevent_event_add(srv->ev, &(hctx->fde_ndx), hctx->fd, FDEVENT_IN);
+ }
+ }
+
+ /* (do not receive request body before GW_AUTHORIZER has run or else
+ * the request body is discarded with handler_ctx_clear() after running
+ * the FastCGI Authorizer) */
+
+ if (hctx->gw_mode != GW_AUTHORIZER
+ && (0 == hctx->wb->bytes_in
+ ? (con->state == CON_STATE_READ_POST || -1 == hctx->wb_reqlen)
+ : (hctx->wb->bytes_in < hctx->wb_reqlen || hctx->wb_reqlen < 0))) {
+ /* leave excess data in con->request_content_queue, which is
+ * buffered to disk if too large and backend can not keep up */
+ /*(64k - 4k to attempt to avoid temporary files
+ * in conjunction with FDEVENT_STREAM_REQUEST_BUFMIN)*/
+ if (hctx->wb->bytes_in - hctx->wb->bytes_out > 65536 - 4096) {
+ if (con->conf.stream_request_body & FDEVENT_STREAM_REQUEST_BUFMIN) {
+ con->conf.stream_request_body &= ~FDEVENT_STREAM_REQUEST_POLLIN;
+ }
+ if (0 != hctx->wb->bytes_in) return HANDLER_WAIT_FOR_EVENT;
+ }
+ else {
+ handler_t r = connection_handle_read_post_state(srv, con);
+ chunkqueue *req_cq = con->request_content_queue;
+ #if 0 /*(not reached since we send 411 Length Required below)*/
+ if (hctx->wb_reqlen < -1 && con->request.content_length >= 0) {
+ /* (completed receiving Transfer-Encoding: chunked) */
+ hctx->wb_reqlen= -hctx->wb_reqlen + con->request.content_length;
+ if (hctx->stdin_append) {
+ handler_t rc = hctx->stdin_append(srv, hctx);
+ if (HANDLER_GO_ON != rc) return rc;
+ }
+ }
+ #endif
+ if ((0 != hctx->wb->bytes_in || -1 == hctx->wb_reqlen)
+ && !chunkqueue_is_empty(req_cq)) {
+ if (hctx->stdin_append) {
+ handler_t rc = hctx->stdin_append(srv, hctx);
+ if (HANDLER_GO_ON != rc) return rc;
+ }
+ else
+ chunkqueue_append_chunkqueue(hctx->wb, req_cq);
+ if (fdevent_event_get_interest(srv->ev,hctx->fd) & FDEVENT_OUT){
+ return (r == HANDLER_GO_ON) ? HANDLER_WAIT_FOR_EVENT : r;
+ }
+ }
+ if (r != HANDLER_GO_ON) return r;
+
+
+ /* XXX: create configurable flag */
+ /* CGI environment requires that Content-Length be set.
+ * Send 411 Length Required if Content-Length missing.
+ * (occurs here if client sends Transfer-Encoding: chunked
+ * and module is flagged to stream request body to backend) */
+ /* proxy currently sends HTTP/1.0 request and ideally should send
+ * Content-Length with request if request body is present, so
+ * send 411 Length Required if Content-Length missing. */
+ if (-1 == con->request.content_length) {
+ return connection_handle_read_post_error(srv, con, 411);
+ }
+ }
+ }
+
+ {
+ handler_t rc =((0==hctx->wb->bytes_in || !chunkqueue_is_empty(hctx->wb))
+ && hctx->state != GW_STATE_CONNECT_DELAYED)
+ ? gw_send_request(srv, hctx)
+ : HANDLER_WAIT_FOR_EVENT;
+ if (HANDLER_WAIT_FOR_EVENT != rc) return rc;
+ }
+
+ if (con->conf.stream_request_body & FDEVENT_STREAM_REQUEST_TCP_FIN)
+ gw_conditional_tcp_fin(srv, hctx);
+
+ return HANDLER_WAIT_FOR_EVENT;
+}
+
+
+static handler_t gw_recv_response(server *srv, gw_handler_ctx *hctx) {
+ connection *con = hctx->remote_conn;
+ gw_proc *proc = hctx->proc;
+ gw_host *host = hctx->host;
+ /*(XXX: make this a configurable flag for other protocols)*/
+ buffer *b = hctx->opts.backend == BACKEND_FASTCGI
+ ? chunk_buffer_acquire()
+ : hctx->response;
+
+ handler_t rc = http_response_read(srv, hctx->remote_conn, &hctx->opts,
+ b, hctx->fd, &hctx->fde_ndx);
+
+ if (b != hctx->response) chunk_buffer_release(b);
+
+ switch (rc) {
+ default:
+ return HANDLER_GO_ON;
+ case HANDLER_FINISHED:
+ if (hctx->gw_mode == GW_AUTHORIZER
+ && (200 == con->http_status || 0 == con->http_status)) {
+ /*
+ * If we are here in AUTHORIZER mode then a request for authorizer
+ * was processed already, and status 200 has been returned. We need
+ * now to handle authorized request.
+ */
+ buffer *physpath = NULL;
+
+ if (!buffer_string_is_empty(host->docroot)) {
+ buffer_copy_buffer(con->physical.doc_root, host->docroot);
+ buffer_copy_buffer(con->physical.basedir, host->docroot);
+
+ buffer_copy_buffer(con->physical.path, host->docroot);
+ buffer_append_string_buffer(con->physical.path, con->uri.path);
+ physpath = con->physical.path;
+ }
+
+ proc->last_used = srv->cur_ts;
+ gw_backend_close(srv, hctx);
+ handler_ctx_clear(hctx);
+
+ /* don't do more than 6 loops here; normally shouldn't happen */
+ if (++con->loops_per_request > 5) {
+ log_error_write(srv, __FILE__, __LINE__, "sb",
+ "too many loops while processing request:",
+ con->request.orig_uri);
+ con->http_status = 500; /* Internal Server Error */
+ con->mode = DIRECT;
+ return HANDLER_FINISHED;
+ }
+
+ /* restart the request so other handlers can process it */
+
+ if (physpath) con->physical.path = NULL;
+ connection_response_reset(srv,con);/*(includes con->http_status=0)*/
+ /* preserve con->physical.path with modified docroot */
+ if (physpath) con->physical.path = physpath;
+
+ /*(FYI: if multiple FastCGI authorizers were to be supported,
+ * next one could be started here instead of restarting request)*/
+
+ con->mode = DIRECT;
+ return HANDLER_COMEBACK;
+ } else {
+ /* we are done */
+ gw_connection_close(srv, hctx);
+ }
+
+ return HANDLER_FINISHED;
+ case HANDLER_COMEBACK: /*(not expected; treat as error)*/
+ case HANDLER_ERROR:
+ /* (optimization to detect backend process exit while processing a
+ * large number of ready events; (this block could be removed)) */
+ if (proc->is_local && 1 == proc->load && proc->pid == hctx->pid
+ && proc->state != PROC_STATE_DIED && 0 == srv->srvconf.max_worker) {
+ /* intentionally check proc->disabed_until before gw_proc_waitpid */
+ if (proc->disabled_until < srv->cur_ts
+ && 0 != gw_proc_waitpid(srv, host, proc)) {
+ if (hctx->conf.debug) {
+ log_error_write(srv, __FILE__, __LINE__, "ssbsdsd",
+ "--- gw spawning",
+ "\n\tsocket", proc->connection_name,
+ "\n\tcurrent:", 1, "/", host->num_procs);
+ }
+
+ if (gw_spawn_connection(srv, host, proc, hctx->conf.debug)) {
+ log_error_write(srv, __FILE__, __LINE__, "s",
+ "respawning failed, will retry later");
+ }
+ }
+ }
+
+ if (con->file_started == 0) {
+ /* nothing has been sent out yet, try to use another child */
+
+ if (hctx->wb->bytes_out == 0 &&
+ hctx->reconnects++ < 5) {
+
+ log_error_write(srv, __FILE__, __LINE__, "ssbsBSBs",
+ "response not received, request not sent",
+ "on socket:", proc->connection_name,
+ "for", con->uri.path, "?", con->uri.query, ", reconnecting");
+
+ return gw_reconnect(srv, hctx);
+ }
+
+ log_error_write(srv, __FILE__, __LINE__, "sosbsBSBs",
+ "response not received, request sent:", hctx->wb->bytes_out,
+ "on socket:", proc->connection_name, "for",
+ con->uri.path, "?", con->uri.query, ", closing connection");
+ } else {
+ log_error_write(srv, __FILE__, __LINE__, "ssbsBSBs",
+ "response already sent out, but backend returned error",
+ "on socket:", proc->connection_name, "for",
+ con->uri.path, "?", con->uri.query, ", terminating connection");
+ }
+
+ if (hctx->backend_error) hctx->backend_error(hctx);
+ http_response_backend_error(srv, con);
+ gw_connection_close(srv, hctx);
+ return HANDLER_FINISHED;
+ }
+}
+
+
+static handler_t gw_handle_fdevent(server *srv, void *ctx, int revents) {
+ gw_handler_ctx *hctx = ctx;
+ connection *con = hctx->remote_conn;
+
+ joblist_append(srv, con);
+
+ if (revents & FDEVENT_IN) {
+ handler_t rc = gw_recv_response(srv, hctx); /*(might invalidate hctx)*/
+ if (rc != HANDLER_GO_ON) return rc; /*(unless HANDLER_GO_ON)*/
+ }
+
+ if (revents & FDEVENT_OUT) {
+ return gw_send_request(srv, hctx); /*(might invalidate hctx)*/
+ }
+
+ /* perhaps this issue is already handled */
+ if (revents & (FDEVENT_HUP|FDEVENT_RDHUP)) {
+ if (hctx->state == GW_STATE_CONNECT_DELAYED) {
+ /* getoptsock will catch this one (right ?)
+ *
+ * if we are in connect we might get an EINPROGRESS
+ * in the first call and an FDEVENT_HUP in the
+ * second round
+ *
+ * FIXME: as it is a bit ugly.
+ *
+ */
+ gw_send_request(srv, hctx);
+ } else if (con->file_started) {
+ /* drain any remaining data from kernel pipe buffers
+ * even if (con->conf.stream_response_body
+ * & FDEVENT_STREAM_RESPONSE_BUFMIN)
+ * since event loop will spin on fd FDEVENT_HUP event
+ * until unregistered. */
+ handler_t rc;
+ const unsigned short flags = con->conf.stream_response_body;
+ con->conf.stream_response_body &= ~FDEVENT_STREAM_RESPONSE_BUFMIN;
+ con->conf.stream_response_body |= FDEVENT_STREAM_RESPONSE_POLLRDHUP;
+ do {
+ rc = gw_recv_response(srv,hctx); /*(might invalidate hctx)*/
+ } while (rc == HANDLER_GO_ON); /*(unless HANDLER_GO_ON)*/
+ con->conf.stream_response_body = flags;
+ return rc; /* HANDLER_FINISHED or HANDLER_ERROR */
+ } else {
+ gw_proc *proc = hctx->proc;
+ log_error_write(srv, __FILE__, __LINE__, "sBSbsbsd",
+ "error: unexpected close of gw connection for",
+ con->uri.path, "?", con->uri.query,
+ "(no gw process on socket:", proc->connection_name, "?)",
+ hctx->state);
+
+ gw_connection_close(srv, hctx);
+ }
+ } else if (revents & FDEVENT_ERR) {
+ log_error_write(srv, __FILE__, __LINE__, "s",
+ "gw: got a FDEVENT_ERR. Don't know why.");
+
+ if (hctx->backend_error) hctx->backend_error(hctx);
+ http_response_backend_error(srv, con);
+ gw_connection_close(srv, hctx);
+ }
+
+ return HANDLER_FINISHED;
+}
+
+handler_t gw_check_extension(server *srv, connection *con, gw_plugin_data *p, int uri_path_handler, size_t hctx_sz) {
+ #if 0 /*(caller must handle)*/
+ if (con->mode != DIRECT) return HANDLER_GO_ON;
+ gw_patch_connection(srv, con, p);
+ if (NULL == p->conf.exts) return HANDLER_GO_ON;
+ #endif
+
+ buffer *fn = uri_path_handler ? con->uri.path : con->physical.path;
+ size_t s_len = buffer_string_length(fn);
+ gw_extension *extension = NULL;
+ gw_host *host = NULL;
+ gw_handler_ctx *hctx;
+ unsigned short gw_mode;
+
+ if (0 == s_len) return HANDLER_GO_ON; /*(not expected)*/
+
+ /* check p->conf.exts_auth list and then p->conf.ext_resp list
+ * (skip p->conf.exts_auth if array is empty
+ * or if GW_AUTHORIZER already ran in this request) */
+ hctx = con->plugin_ctx[p->id];
+ /*(hctx not NULL if GW_AUTHORIZER ran; hctx->ext_auth check is redundant)*/
+ gw_mode = (NULL == hctx || NULL == hctx->ext_auth)
+ ? 0 /*GW_AUTHORIZER p->conf.exts_auth will be searched next*/
+ : GW_AUTHORIZER; /*GW_RESPONDER p->conf.exts_resp will be searched next*/
+
+ do {
+
+ gw_exts *exts;
+ if (0 == gw_mode) {
+ gw_mode = GW_AUTHORIZER;
+ exts = p->conf.exts_auth;
+ } else {
+ gw_mode = GW_RESPONDER;
+ exts = p->conf.exts_resp;
+ }
+
+ if (0 == exts->used) continue;
+
+ /* gw.map-extensions maps extensions to existing gw.server entries
+ *
+ * gw.map-extensions = ( ".php3" => ".php" )
+ *
+ * gw.server = ( ".php" => ... )
+ *
+ * */
+
+ /* check if extension-mapping matches */
+ if (p->conf.ext_mapping) {
+ data_string *ds =
+ (data_string *)array_match_key_suffix(p->conf.ext_mapping, fn);
+ if (NULL != ds) {
+ /* found a mapping */
+ /* check if we know the extension */
+ size_t k;
+ for (k = 0; k < exts->used; ++k) {
+ extension = exts->exts[k];
+
+ if (buffer_is_equal(ds->value, extension->key)) {
+ break;
+ }
+ }
+
+ if (k == exts->used) {
+ /* found nothing */
+ extension = NULL;
+ }
+ }
+ }
+
+ if (extension == NULL) {
+ size_t uri_path_len = buffer_string_length(con->uri.path);
+
+ /* check if extension matches */
+ for (size_t k = 0; k < exts->used; ++k) {
+ gw_extension *ext = exts->exts[k];
+ size_t ct_len = buffer_string_length(ext->key);
+
+ /* check _url_ in the form "/gw_pattern" */
+ if (ext->key->ptr[0] == '/') {
+ if (ct_len <= uri_path_len
+ && 0==memcmp(con->uri.path->ptr,ext->key->ptr,ct_len)){
+ extension = ext;
+ break;
+ }
+ } else if (ct_len <= s_len
+ && 0 == memcmp(fn->ptr + s_len - ct_len,
+ ext->key->ptr, ct_len)) {
+ /* check extension in the form ".fcg" */
+ extension = ext;
+ break;
+ }
+ }
+ }
+
+ } while (NULL == extension && gw_mode != GW_RESPONDER);
+
+ /* extension doesn't match */
+ if (NULL == extension) {
+ return HANDLER_GO_ON;
+ }
+
+ /* check if we have at least one server for this extension up and running */
+ host = gw_host_get(srv, con, extension, p->conf.balance, p->conf.debug);
+ if (NULL == host) {
+ return HANDLER_FINISHED;
+ }
+
+ /* a note about no handler is not sent yet */
+ extension->note_is_sent = 0;
+
+ /*
+ * if check-local is disabled, use the uri.path handler
+ *
+ */
+
+ /* init handler-context */
+ if (uri_path_handler) {
+ if (host->check_local != 0) {
+ return HANDLER_GO_ON;
+ } else {
+ /* do not split path info for authorizer */
+ if (gw_mode != GW_AUTHORIZER) {
+ /* the prefix is the SCRIPT_NAME,
+ * everything from start to the next slash
+ * this is important for check-local = "disable"
+ *
+ * if prefix = /admin.gw
+ *
+ * /admin.gw/foo/bar
+ *
+ * SCRIPT_NAME = /admin.gw
+ * PATH_INFO = /foo/bar
+ *
+ * if prefix = /cgi-bin/
+ *
+ * /cgi-bin/foo/bar
+ *
+ * SCRIPT_NAME = /cgi-bin/foo
+ * PATH_INFO = /bar
+ *
+ * if prefix = /, and fix-root-path-name is enable
+ *
+ * /cgi-bin/foo/bar
+ *
+ * SCRIPT_NAME = /cgi-bin/foo
+ * PATH_INFO = /bar
+ *
+ */
+ char *pathinfo;
+
+ /* the rewrite is only done for /prefix/? matches */
+ if (host->fix_root_path_name && extension->key->ptr[0] == '/'
+ && extension->key->ptr[1] == '\0'){
+ buffer_copy_buffer(con->request.pathinfo, con->uri.path);
+ buffer_clear(con->uri.path);
+ } else if (extension->key->ptr[0] == '/'
+ && buffer_string_length(con->uri.path)
+ > buffer_string_length(extension->key)
+ && (pathinfo =
+ strchr(con->uri.path->ptr
+ + buffer_string_length(extension->key),
+ '/')) != NULL) {
+ /* rewrite uri.path and pathinfo */
+
+ buffer_copy_string(con->request.pathinfo, pathinfo);
+ buffer_string_set_length(
+ con->uri.path,
+ buffer_string_length(con->uri.path)
+ - buffer_string_length(con->request.pathinfo));
+ }
+ }
+ }
+ }
+
+ if (!hctx) hctx = handler_ctx_init(hctx_sz);
+
+ hctx->remote_conn = con;
+ hctx->plugin_data = p;
+ hctx->host = host;
+ hctx->proc = NULL;
+ hctx->ext = extension;
+ gw_host_assign(srv, host);
+
+ hctx->gw_mode = gw_mode;
+ if (gw_mode == GW_AUTHORIZER) {
+ hctx->ext_auth = hctx->ext;
+ }
+
+ /*hctx->conf.exts = p->conf.exts;*/
+ /*hctx->conf.exts_auth = p->conf.exts_auth;*/
+ /*hctx->conf.exts_resp = p->conf.exts_resp;*/
+ /*hctx->conf.ext_mapping = p->conf.ext_mapping;*/
+ hctx->conf.balance = p->conf.balance;
+ hctx->conf.proto = p->conf.proto;
+ hctx->conf.debug = p->conf.debug;
+
+ hctx->opts.fdfmt = S_IFSOCK;
+ hctx->opts.authorizer = (gw_mode == GW_AUTHORIZER);
+ hctx->opts.local_redir = 0;
+ hctx->opts.xsendfile_allow = host->xsendfile_allow;
+ hctx->opts.xsendfile_docroot = host->xsendfile_docroot;
+
+ con->plugin_ctx[p->id] = hctx;
+
+ con->mode = p->id;
+
+ if (con->conf.log_request_handling) {
+ log_error_write(srv, __FILE__, __LINE__, "s", "handling it in mod_gw");
+ }
+
+ return HANDLER_GO_ON;
+}
+
+static void gw_handle_trigger_host(server *srv, gw_host *host, int debug) {
+ /*
+ * TODO:
+ *
+ * - add timeout for a connect to a non-gw process
+ * (use state_timestamp + state)
+ *
+ * perhaps we should kill a connect attempt after 10-15 seconds
+ *
+ * currently we wait for the TCP timeout which is 180 seconds on Linux
+ */
+
+ /* check each child proc to detect if proc exited */
+
+ gw_proc *proc;
+ time_t idle_timestamp;
+ int overload = 1;
+
+ for (proc = host->first; proc; proc = proc->next) {
+ gw_proc_waitpid(srv, host, proc);
+ }
+
+ gw_restart_dead_procs(srv, host, debug, 1);
+
+ /* check if adaptive spawning enabled */
+ if (host->min_procs == host->max_procs) return;
+ if (buffer_string_is_empty(host->bin_path)) return;
+
+ for (proc = host->first; proc; proc = proc->next) {
+ if (proc->load <= host->max_load_per_proc) {
+ overload = 0;
+ break;
+ }
+ }
+
+ if (overload && host->num_procs && host->num_procs < host->max_procs) {
+ /* overload, spawn new child */
+ if (debug) {
+ log_error_write(srv, __FILE__, __LINE__, "s",
+ "overload detected, spawning a new child");
+ }
+
+ gw_proc_spawn(srv, host, debug);
+ }
+
+ idle_timestamp = srv->cur_ts - host->idle_timeout;
+ for (proc = host->first; proc; proc = proc->next) {
+ if (host->num_procs <= host->min_procs) break;
+ if (0 != proc->load) continue;
+ if (proc->pid <= 0) continue;
+ if (proc->last_used >= idle_timestamp) continue;
+
+ /* terminate proc that has been idling for a long time */
+ if (debug) {
+ log_error_write(srv, __FILE__, __LINE__, "ssbsd",
+ "idle-timeout reached, terminating child:",
+ "socket:", proc->unixsocket, "pid", proc->pid);
+ }
+
+ gw_proc_kill(srv, host, proc);
+
+ /* proc is now in unused, let next second handle next process */
+ break;
+ }
+
+ for (proc = host->unused_procs; proc; proc = proc->next) {
+ gw_proc_waitpid(srv, host, proc);
+ }
+}
+
+static void gw_handle_trigger_exts(server *srv, gw_exts *exts, int debug) {
+ for (size_t j = 0; j < exts->used; ++j) {
+ gw_extension *ex = exts->exts[j];
+ for (size_t n = 0; n < ex->used; ++n) {
+ gw_handle_trigger_host(srv, ex->hosts[n], debug);
+ }
+ }
+}
+
+static void gw_handle_trigger_exts_wkr(server *srv, gw_exts *exts) {
+ for (size_t j = 0; j < exts->used; ++j) {
+ gw_extension * const ex = exts->exts[j];
+ for (size_t n = 0; n < ex->used; ++n) {
+ gw_host * const host = ex->hosts[n];
+ for (gw_proc *proc = host->first; proc; proc = proc->next) {
+ if (proc->state == PROC_STATE_OVERLOADED)
+ gw_proc_check_enable(srv, host, proc);
+ }
+ }
+ }
+}
+
+handler_t gw_handle_trigger(server *srv, void *p_d) {
+ gw_plugin_data *p = p_d;
+ int wkr = (0 != srv->srvconf.max_worker && p->srv_pid != srv->pid);
+
+ for (size_t i = 0; i < srv->config_context->used; i++) {
+ gw_plugin_config *conf = p->config_storage[i];
+ gw_exts *exts = conf->exts;
+ int debug = conf->debug ? conf->debug : p->config_storage[0]->debug;
+ if (NULL == exts) continue;
+ wkr
+ ? gw_handle_trigger_exts_wkr(srv, exts)
+ : gw_handle_trigger_exts(srv, exts, debug);
+ }
+
+ return HANDLER_GO_ON;
+}
+
+handler_t gw_handle_waitpid_cb(server *srv, void *p_d, pid_t pid, int status) {
+ gw_plugin_data *p = p_d;
+ if (0 != srv->srvconf.max_worker && p->srv_pid != srv->pid)
+ return HANDLER_GO_ON;
+
+ for (size_t i = 0; i < srv->config_context->used; ++i) {
+ gw_plugin_config *conf = p->config_storage[i];
+ gw_exts *exts = conf->exts;
+ int debug = conf->debug ? conf->debug : p->config_storage[0]->debug;
+ if (NULL == exts) continue;
+ for (size_t j = 0; j < exts->used; ++j) {
+ gw_extension *ex = exts->exts[j];
+ for (size_t n = 0; n < ex->used; ++n) {
+ gw_host *host = ex->hosts[n];
+ gw_proc *proc;
+ for (proc = host->first; proc; proc = proc->next) {
+ if (!proc->is_local || proc->pid != pid) continue;
+
+ gw_proc_waitpid_log(srv, host, proc, status);
+ gw_proc_set_state(host, proc, PROC_STATE_DIED);
+ proc->pid = 0;
+
+ /* restart, but avoid spinning if child exits too quickly */
+ if (proc->disabled_until < srv->cur_ts) {
+ if (proc->state != PROC_STATE_KILLED)
+ proc->disabled_until = srv->cur_ts;
+ if (gw_spawn_connection(srv, host, proc, debug)) {
+ log_error_write(srv, __FILE__, __LINE__, "s",
+ "ERROR: spawning gw failed.");
+ }
+ }
+
+ return HANDLER_FINISHED;
+ }
+ for (proc = host->unused_procs; proc; proc = proc->next) {
+ if (!proc->is_local || proc->pid != pid) continue;
+
+ gw_proc_waitpid_log(srv, host, proc, status);
+ if (proc->state != PROC_STATE_KILLED)
+ proc->disabled_until = srv->cur_ts;
+ gw_proc_set_state(host, proc, PROC_STATE_DIED);
+ proc->pid = 0;
+ return HANDLER_FINISHED;
+ }
+ }
+ }
+ }
+
+ return HANDLER_GO_ON;
+}