diff options
author | MCApollo <34170230+MCApollo@users.noreply.github.com> | 2019-04-29 17:53:00 +0000 |
---|---|---|
committer | MCApollo <34170230+MCApollo@users.noreply.github.com> | 2019-04-29 17:53:00 +0000 |
commit | 59f5fd20e2f59ae186d5a461aef7782dd55e4e7b (patch) | |
tree | 10475faf0b6dcb812b64da679f4ae8ff01c45874 /data/lighttpd/lighttpd-1.4.53/src/gw_backend.c | |
parent | f06e297cd175cca39745f6d1970225ae88aa68f1 (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.c | 2604 |
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; +} |