|
|
|
#include "first.h"
|
|
|
|
|
|
|
|
#include "base.h"
|
|
|
|
#include "buffer.h"
|
|
|
|
#include "network.h"
|
|
|
|
#include "log.h"
|
|
|
|
#include "rand.h"
|
|
|
|
#include "chunk.h"
|
|
|
|
#include "h2.h" /* h2_send_1xx() */
|
|
|
|
#include "http_range.h" /* http_range_config_allow_http10() */
|
|
|
|
#include "fdevent.h"
|
|
|
|
#include "fdlog.h"
|
|
|
|
#include "connections.h"
|
|
|
|
#include "sock_addr.h"
|
|
|
|
#include "stat_cache.h"
|
|
|
|
#include "plugin.h"
|
|
|
|
#include "plugins.h"
|
|
|
|
#include "plugin_config.h" /* config_plugin_value_tobool() */
|
|
|
|
#include "network_write.h" /* network_write_show_handlers() */
|
|
|
|
#include "reqpool.h" /* request_pool_init() request_pool_free() */
|
|
|
|
#include "response.h" /* http_response_send_1xx_cb_set() strftime_cache_reset() */
|
|
|
|
|
|
|
|
#ifdef HAVE_VERSIONSTAMP_H
|
|
|
|
# include "versionstamp.h"
|
|
|
|
#else
|
|
|
|
# define REPO_VERSION ""
|
|
|
|
#endif
|
|
|
|
|
|
|
|
#define PACKAGE_DESC PACKAGE_NAME "/" PACKAGE_VERSION REPO_VERSION
|
|
|
|
static const buffer default_server_tag =
|
|
|
|
{ PACKAGE_DESC "\0server", sizeof(PACKAGE_DESC), 0 };
|
|
|
|
|
|
|
|
#include <sys/types.h>
|
|
|
|
#include <sys/stat.h>
|
|
|
|
#include "sys-setjmp.h"
|
|
|
|
#include "sys-time.h"
|
|
|
|
|
|
|
|
#include <string.h>
|
|
|
|
#include <errno.h>
|
|
|
|
#include <fcntl.h>
|
|
|
|
#include <unistd.h>
|
|
|
|
#include <stdlib.h>
|
|
|
|
#include <signal.h>
|
|
|
|
#include <locale.h>
|
|
|
|
|
|
|
|
#include <stdio.h>
|
|
|
|
|
|
|
|
#ifdef HAVE_GETOPT_H
|
|
|
|
# include <getopt.h>
|
|
|
|
#endif
|
|
|
|
|
|
|
|
#ifdef HAVE_VALGRIND_VALGRIND_H
|
|
|
|
# include <valgrind/valgrind.h>
|
|
|
|
#endif
|
|
|
|
|
|
|
|
#ifdef HAVE_PWD_H
|
|
|
|
# include <grp.h>
|
|
|
|
# include <pwd.h>
|
|
|
|
#endif
|
|
|
|
|
|
|
|
#ifdef HAVE_SYS_LOADAVG_H
|
|
|
|
# include <sys/loadavg.h>
|
|
|
|
#endif
|
|
|
|
|
|
|
|
#ifdef HAVE_SYS_RESOURCE_H
|
|
|
|
# include <sys/resource.h>
|
|
|
|
#endif
|
|
|
|
|
|
|
|
#ifdef HAVE_SYS_PRCTL_H
|
|
|
|
# include <sys/prctl.h>
|
|
|
|
#endif
|
|
|
|
#ifdef HAVE_SYS_PROCCTL_H
|
|
|
|
# include <sys/procctl.h>
|
|
|
|
#endif
|
|
|
|
#ifdef HAVE_PRIV_H
|
|
|
|
# include <priv.h>
|
|
|
|
#endif
|
|
|
|
|
|
|
|
#ifdef HAVE_MALLOC_H
|
|
|
|
#ifndef LIGHTTPD_STATIC
|
|
|
|
#ifdef HAVE_DLFCN_H
|
|
|
|
#include <dlfcn.h>
|
|
|
|
#endif
|
|
|
|
#endif
|
|
|
|
#include <malloc.h>
|
|
|
|
#if defined(HAVE_MALLOC_TRIM)
|
|
|
|
static int(*malloc_trim_fn)(size_t);
|
|
|
|
static size_t malloc_top_pad;
|
|
|
|
#endif
|
|
|
|
#endif
|
|
|
|
|
|
|
|
#include "sys-crypto.h"
|
|
|
|
#if defined(USE_OPENSSL_CRYPTO) \
|
|
|
|
|| defined(USE_MBEDTLS_CRYPTO) \
|
|
|
|
|| defined(USE_NSS_CRYPTO) \
|
|
|
|
|| defined(USE_GNUTLS_CRYPTO) \
|
|
|
|
|| defined(USE_WOLFTLS_CRYPTO)
|
|
|
|
#define TEXT_SSL " (ssl)"
|
|
|
|
#else
|
|
|
|
#define TEXT_SSL
|
|
|
|
#endif
|
|
|
|
|
|
|
|
#ifndef __sgi
|
|
|
|
/* IRIX doesn't like the alarm based time() optimization */
|
|
|
|
/* #define USE_ALARM */
|
|
|
|
#endif
|
|
|
|
|
|
|
|
static int oneshot_fd = 0;
|
|
|
|
static int oneshot_fdout = -1;
|
|
|
|
static fdnode *oneshot_fdn = NULL;
|
|
|
|
static int (*oneshot_read_cq)(connection *con, chunkqueue *cq, off_t max_bytes);
|
|
|
|
static volatile int pid_fd = -2;
|
|
|
|
static server_socket_array graceful_sockets;
|
|
|
|
static server_socket_array inherited_sockets;
|
|
|
|
static volatile sig_atomic_t graceful_restart = 0;
|
|
|
|
static volatile sig_atomic_t graceful_shutdown = 0;
|
|
|
|
static volatile sig_atomic_t srv_shutdown = 0;
|
|
|
|
static volatile sig_atomic_t handle_sig_child = 0;
|
|
|
|
static volatile sig_atomic_t handle_sig_alarm = 1;
|
|
|
|
static volatile sig_atomic_t handle_sig_hup = 0;
|
|
|
|
static int idle_limit = 0;
|
|
|
|
|
|
|
|
#if defined(HAVE_SIGACTION) && defined(SA_SIGINFO)
|
|
|
|
static volatile siginfo_t last_sigterm_info;
|
|
|
|
static volatile siginfo_t last_sighup_info;
|
|
|
|
|
|
|
|
static void sigaction_handler(int sig, siginfo_t *si, void *context) {
|
|
|
|
static const siginfo_t empty_siginfo;
|
|
|
|
UNUSED(context);
|
|
|
|
|
|
|
|
if (!si) *(const siginfo_t **)&si = &empty_siginfo;
|
|
|
|
|
|
|
|
switch (sig) {
|
|
|
|
case SIGTERM:
|
|
|
|
srv_shutdown = 1;
|
|
|
|
last_sigterm_info = *si;
|
|
|
|
break;
|
|
|
|
case SIGUSR1:
|
|
|
|
if (!graceful_shutdown) {
|
|
|
|
graceful_restart = 1;
|
|
|
|
graceful_shutdown = 1;
|
|
|
|
last_sigterm_info = *si;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case SIGINT:
|
|
|
|
if (graceful_shutdown) {
|
|
|
|
if (2 == graceful_restart)
|
|
|
|
graceful_restart = 1;
|
|
|
|
else
|
|
|
|
srv_shutdown = 1;
|
|
|
|
} else {
|
|
|
|
graceful_shutdown = 1;
|
|
|
|
}
|
|
|
|
last_sigterm_info = *si;
|
|
|
|
|
|
|
|
break;
|
|
|
|
case SIGALRM:
|
|
|
|
handle_sig_alarm = 1;
|
|
|
|
break;
|
|
|
|
case SIGHUP:
|
|
|
|
handle_sig_hup = 1;
|
|
|
|
last_sighup_info = *si;
|
|
|
|
break;
|
|
|
|
case SIGCHLD:
|
|
|
|
handle_sig_child = 1;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
#elif defined(HAVE_SIGNAL) || defined(HAVE_SIGACTION)
|
|
|
|
static void signal_handler(int sig) {
|
|
|
|
switch (sig) {
|
|
|
|
case SIGTERM: srv_shutdown = 1; break;
|
|
|
|
case SIGUSR1:
|
|
|
|
if (!graceful_shutdown) {
|
|
|
|
graceful_restart = 1;
|
|
|
|
graceful_shutdown = 1;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case SIGINT:
|
|
|
|
if (graceful_shutdown) {
|
|
|
|
if (2 == graceful_restart)
|
|
|
|
graceful_restart = 1;
|
|
|
|
else
|
|
|
|
srv_shutdown = 1;
|
|
|
|
} else {
|
|
|
|
graceful_shutdown = 1;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case SIGALRM: handle_sig_alarm = 1; break;
|
|
|
|
case SIGHUP: handle_sig_hup = 1; break;
|
|
|
|
case SIGCHLD: handle_sig_child = 1; break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
|
|
|
|
#ifdef HAVE_FORK
|
|
|
|
static int daemonize(void) {
|
|
|
|
int pipefd[2];
|
|
|
|
pid_t pid;
|
|
|
|
#ifdef SIGTTOU
|
|
|
|
signal(SIGTTOU, SIG_IGN);
|
|
|
|
#endif
|
|
|
|
#ifdef SIGTTIN
|
|
|
|
signal(SIGTTIN, SIG_IGN);
|
|
|
|
#endif
|
|
|
|
#ifdef SIGTSTP
|
|
|
|
signal(SIGTSTP, SIG_IGN);
|
|
|
|
#endif
|
|
|
|
|
|
|
|
if (fdevent_pipe_cloexec(pipefd, 64) < 0) exit(-1);
|
|
|
|
|
|
|
|
if (0 > (pid = fork())) exit(-1);
|
|
|
|
|
|
|
|
if (0 < pid) {
|
|
|
|
char buf;
|
|
|
|
ssize_t bytes;
|
|
|
|
|
|
|
|
close(pipefd[1]);
|
|
|
|
/* parent waits for grandchild to be ready */
|
|
|
|
do {
|
|
|
|
bytes = read(pipefd[0], &buf, sizeof(buf));
|
|
|
|
} while (bytes < 0 && EINTR == errno);
|
|
|
|
close(pipefd[0]);
|
|
|
|
|
|
|
|
if (bytes <= 0) {
|
|
|
|
/* closed fd (without writing) == failure in grandchild */
|
|
|
|
fputs("daemonized server failed to start; check error log for details\n", stderr);
|
|
|
|
exit(-1);
|
|
|
|
}
|
|
|
|
|
|
|
|
exit(0);
|
|
|
|
}
|
|
|
|
|
|
|
|
close(pipefd[0]);
|
|
|
|
|
|
|
|
if (-1 == setsid()) exit(0);
|
|
|
|
|
|
|
|
signal(SIGHUP, SIG_IGN);
|
|
|
|
|
|
|
|
if (0 != fork()) exit(0);
|
|
|
|
|
|
|
|
if (0 != chdir("/")) exit(0);
|
|
|
|
|
|
|
|
return pipefd[1];
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
|
|
|
|
static int clockid_mono_coarse = 0;
|
|
|
|
|
|
|
|
static unix_time64_t
|
|
|
|
server_monotonic_secs (void)
|
|
|
|
{
|
|
|
|
unix_timespec64_t ts;
|
|
|
|
return (0 == log_clock_gettime(clockid_mono_coarse, &ts))
|
|
|
|
? ts.tv_sec
|
|
|
|
: log_monotonic_secs;
|
|
|
|
}
|
|
|
|
|
|
|
|
static unix_time64_t
|
|
|
|
server_epoch_secs (server * const srv, unix_time64_t mono_ts_delta)
|
|
|
|
{
|
|
|
|
const unix_time64_t cur_ts = log_epoch_secs;
|
|
|
|
const unix_time64_t new_ts = TIME64_CAST(time(NULL));
|
|
|
|
const unix_time64_t new_ts_adj = new_ts - mono_ts_delta;
|
|
|
|
/* attempt to detect large clock jump */
|
|
|
|
if (new_ts_adj < cur_ts || new_ts_adj - cur_ts > 300) { /*(5 mins)*/
|
|
|
|
log_error(srv->errh, __FILE__, __LINE__,
|
|
|
|
"warning: clock jumped %lld secs",
|
|
|
|
(long long)((int64_t)new_ts_adj - (int64_t)cur_ts));
|
|
|
|
int delta = /*(30 mins default)*/
|
|
|
|
config_feature_int(srv, "server.clock-jump-restart", 1800);
|
|
|
|
if (delta && (new_ts_adj > cur_ts
|
|
|
|
? new_ts_adj-cur_ts
|
|
|
|
: cur_ts-new_ts_adj) > delta) {
|
|
|
|
log_error(srv->errh, __FILE__, __LINE__,
|
|
|
|
"attempting graceful restart in < ~5 seconds, else hard restart");
|
|
|
|
srv->graceful_expire_ts = log_monotonic_secs + 5;
|
|
|
|
raise(SIGUSR1);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return new_ts;
|
|
|
|
}
|
|
|
|
|
|
|
|
__attribute_cold__
|
|
|
|
__attribute_noinline__
|
|
|
|
__attribute_returns_nonnull__
|
|
|
|
static server *server_init(void) {
|
|
|
|
server *srv = calloc(1, sizeof(*srv));
|
|
|
|
force_assert(srv);
|
|
|
|
|
|
|
|
srv->tmp_buf = buffer_init();
|
|
|
|
|
|
|
|
strftime_cache_reset();
|
|
|
|
|
|
|
|
li_rand_reseed();
|
|
|
|
|
|
|
|
srv->startup_ts = log_epoch_secs = TIME64_CAST(time(NULL));
|
|
|
|
#ifdef HAVE_CLOCK_GETTIME
|
|
|
|
unix_timespec64_t ts;
|
|
|
|
UNUSED(&ts);
|
|
|
|
#ifdef CLOCK_MONOTONIC_COARSE
|
|
|
|
if (0 == log_clock_gettime(CLOCK_MONOTONIC_COARSE, &ts))
|
|
|
|
clockid_mono_coarse = CLOCK_MONOTONIC_COARSE;
|
|
|
|
else
|
|
|
|
#endif
|
|
|
|
#ifdef CLOCK_MONOTONIC_RAW_APPROX
|
|
|
|
if (0 == log_clock_gettime(CLOCK_MONOTONIC_RAW_APPROX, &ts))
|
|
|
|
clockid_mono_coarse = CLOCK_MONOTONIC_RAW_APPROX;
|
|
|
|
else
|
|
|
|
#endif
|
|
|
|
#ifdef CLOCK_MONOTONIC_RAW
|
|
|
|
if (0 == log_clock_gettime(CLOCK_MONOTONIC_RAW, &ts))
|
|
|
|
clockid_mono_coarse = CLOCK_MONOTONIC_RAW;
|
|
|
|
else
|
|
|
|
#endif
|
|
|
|
clockid_mono_coarse = CLOCK_MONOTONIC;
|
|
|
|
#endif
|
|
|
|
log_monotonic_secs = server_monotonic_secs();
|
|
|
|
|
|
|
|
srv->errh = log_set_global_errh(NULL, 0);
|
|
|
|
|
|
|
|
config_init(srv);
|
|
|
|
|
|
|
|
srv->request_env = plugins_call_handle_request_env;
|
|
|
|
srv->plugins_request_reset = plugins_call_handle_request_reset;
|
|
|
|
|
|
|
|
srv->loadavg[0] = 0.0;
|
|
|
|
srv->loadavg[1] = 0.0;
|
|
|
|
srv->loadavg[2] = 0.0;
|
|
|
|
srv->stdin_fd = -1;
|
|
|
|
srv->default_server_tag = &default_server_tag;
|
|
|
|
|
|
|
|
log_con_jqueue = (connection *)(uintptr_t)&log_con_jqueue;/*(sentinel)*/
|
|
|
|
|
|
|
|
return srv;
|
|
|
|
}
|
|
|
|
|
|
|
|
__attribute_cold__
|
|
|
|
__attribute_noinline__
|
|
|
|
static void server_free(server *srv) {
|
|
|
|
if (oneshot_fd > 0) {
|
|
|
|
if (oneshot_fdn) {
|
|
|
|
fdevent_fdnode_event_del(srv->ev, oneshot_fdn);
|
|
|
|
fdevent_unregister(srv->ev, oneshot_fd);
|
|
|
|
oneshot_fdn = NULL;
|
|
|
|
}
|
|
|
|
close(oneshot_fd);
|
|
|
|
}
|
|
|
|
if (oneshot_fdout >= 0) {
|
|
|
|
close(oneshot_fdout);
|
|
|
|
}
|
|
|
|
if (srv->stdin_fd >= 0) {
|
|
|
|
close(srv->stdin_fd);
|
|
|
|
}
|
|
|
|
|
|
|
|
buffer_free(srv->tmp_buf);
|
|
|
|
|
|
|
|
fdevent_free(srv->ev);
|
|
|
|
|
|
|
|
config_free(srv);
|
|
|
|
|
|
|
|
stat_cache_free();
|
|
|
|
|
|
|
|
li_rand_cleanup();
|
|
|
|
chunkqueue_chunk_pool_free();
|
|
|
|
|
|
|
|
if (srv->errh != log_set_global_errh(NULL, 0))
|
|
|
|
fdlog_free(srv->errh);
|
|
|
|
free(srv);
|
|
|
|
}
|
|
|
|
|
|
|
|
__attribute_cold__
|
|
|
|
__attribute_noinline__
|
|
|
|
static void remove_pid_file(server *srv) {
|
|
|
|
if (pid_fd <= -2) return;
|
[multiple] reduce redundant NULL buffer checks
This commit is a large set of code changes and results in removal of
hundreds, perhaps thousands, of CPU instructions, a portion of which
are on hot code paths.
Most (buffer *) used by lighttpd are not NULL, especially since buffers
were inlined into numerous larger structs such as request_st and chunk.
In the small number of instances where that is not the case, a NULL
check is often performed earlier in a function where that buffer is
later used with a buffer_* func. In the handful of cases that remained,
a NULL check was added, e.g. with r->http_host and r->conf.server_tag.
- check for empty strings at config time and set value to NULL if blank
string will be ignored at runtime; at runtime, simple pointer check
for NULL can be used to check for a value that has been set and is not
blank ("")
- use buffer_is_blank() instead of buffer_string_is_empty(),
and use buffer_is_unset() instead of buffer_is_empty(),
where buffer is known not to be NULL so that NULL check can be skipped
- use buffer_clen() instead of buffer_string_length() when buffer is
known not to be NULL (to avoid NULL check at runtime)
- use buffer_truncate() instead of buffer_string_set_length() to
truncate string, and use buffer_extend() to extend
Examples where buffer known not to be NULL:
- cpv->v.b from config_plugin_values_init is not NULL if T_CONFIG_BOOL
(though we might set it to NULL if buffer_is_blank(cpv->v.b))
- address of buffer is arg (&foo)
(compiler optimizer detects this in most, but not all, cases)
- buffer is checked for NULL earlier in func
- buffer is accessed in same scope without a NULL check (e.g. b->ptr)
internal behavior change:
callers must not pass a NULL buffer to some funcs.
- buffer_init_buffer() requires non-null args
- buffer_copy_buffer() requires non-null args
- buffer_append_string_buffer() requires non-null args
- buffer_string_space() requires non-null arg
12 months ago
|
|
|
if (srv->srvconf.pid_file && 0 <= pid_fd) {
|
|
|
|
if (0 != ftruncate(pid_fd, 0)) {
|
|
|
|
log_perror(srv->errh, __FILE__, __LINE__,
|
|
|
|
"ftruncate failed for: %s", srv->srvconf.pid_file->ptr);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (0 <= pid_fd) {
|
|
|
|
close(pid_fd);
|
|
|
|
pid_fd = -1;
|
|
|
|
}
|
[multiple] reduce redundant NULL buffer checks
This commit is a large set of code changes and results in removal of
hundreds, perhaps thousands, of CPU instructions, a portion of which
are on hot code paths.
Most (buffer *) used by lighttpd are not NULL, especially since buffers
were inlined into numerous larger structs such as request_st and chunk.
In the small number of instances where that is not the case, a NULL
check is often performed earlier in a function where that buffer is
later used with a buffer_* func. In the handful of cases that remained,
a NULL check was added, e.g. with r->http_host and r->conf.server_tag.
- check for empty strings at config time and set value to NULL if blank
string will be ignored at runtime; at runtime, simple pointer check
for NULL can be used to check for a value that has been set and is not
blank ("")
- use buffer_is_blank() instead of buffer_string_is_empty(),
and use buffer_is_unset() instead of buffer_is_empty(),
where buffer is known not to be NULL so that NULL check can be skipped
- use buffer_clen() instead of buffer_string_length() when buffer is
known not to be NULL (to avoid NULL check at runtime)
- use buffer_truncate() instead of buffer_string_set_length() to
truncate string, and use buffer_extend() to extend
Examples where buffer known not to be NULL:
- cpv->v.b from config_plugin_values_init is not NULL if T_CONFIG_BOOL
(though we might set it to NULL if buffer_is_blank(cpv->v.b))
- address of buffer is arg (&foo)
(compiler optimizer detects this in most, but not all, cases)
- buffer is checked for NULL earlier in func
- buffer is accessed in same scope without a NULL check (e.g. b->ptr)
internal behavior change:
callers must not pass a NULL buffer to some funcs.
- buffer_init_buffer() requires non-null args
- buffer_copy_buffer() requires non-null args
- buffer_append_string_buffer() requires non-null args
- buffer_string_space() requires non-null arg
12 months ago
|
|
|
if (srv->srvconf.pid_file && !srv->srvconf.changeroot) {
|
|
|
|
if (0 != unlink(srv->srvconf.pid_file->ptr)) {
|
|
|
|
if (errno != EACCES && errno != EPERM) {
|
|
|
|
log_perror(srv->errh, __FILE__, __LINE__,
|
|
|
|
"unlink failed for: %s", srv->srvconf.pid_file->ptr);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
__attribute_cold__
|
|
|
|
static server_socket * server_oneshot_getsock(server *srv, sock_addr *cnt_addr) {
|
|
|
|
server_socket *srv_socket, *srv_socket_wild = NULL;
|
|
|
|
for (uint32_t i = 0; i < srv->srv_sockets.used; ++i) {
|
|
|
|
srv_socket = srv->srv_sockets.ptr[i];
|
|
|
|
if (!sock_addr_is_port_eq(&srv_socket->addr,cnt_addr)) continue;
|
|
|
|
if (sock_addr_is_addr_eq(&srv_socket->addr,cnt_addr)) return srv_socket;
|
|
|
|
|
|
|
|
if (NULL != srv_socket_wild) continue;
|
|
|
|
if (sock_addr_is_addr_wildcard(&srv_socket->addr)) {
|
|
|
|
srv_socket_wild = srv_socket;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (NULL != srv_socket_wild) {
|
|
|
|
return srv_socket_wild;
|
|
|
|
} else if (srv->srv_sockets.used) {
|
|
|
|
return srv->srv_sockets.ptr[0];
|
|
|
|
} else {
|
|
|
|
log_error(srv->errh, __FILE__, __LINE__, "no sockets configured");
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static int server_oneshot_read_cq(connection *con, chunkqueue *cq, off_t max_bytes) {
|
|
|
|
/* temporary set con->fd to oneshot_fd (fd input) rather than outshot_fdout
|
|
|
|
* (lighttpd generally assumes operation on sockets, so this is a kludge) */
|
|
|
|
int fd = con->fd;
|
|
|
|
con->fd = oneshot_fdn->fd;
|
|
|
|
int rc = oneshot_read_cq(con, cq, max_bytes);
|
|
|
|
con->fd = fd;
|
|
|
|
|
|
|
|
/* note: optimistic reads (elsewhere) may or may not be enough to re-enable
|
|
|
|
* read interest after FDEVENT_IN interest was paused for other reasons */
|
|
|
|
|
|
|
|
const int events = fdevent_fdnode_interest(oneshot_fdn);
|
|
|
|
int n = con->is_readable > 0 ? 0 : FDEVENT_IN;
|
|
|
|
if (events & FDEVENT_RDHUP)
|
|
|
|
n |= FDEVENT_RDHUP;
|
|
|
|
fdevent_fdnode_event_set(con->srv->ev, oneshot_fdn, n);
|
|
|
|
return rc;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static handler_t server_oneshot_handle_fdevent(void *context, int revents) {
|
|
|
|
connection *con = context;
|
|
|
|
|
|
|
|
/* note: not sync'd with con->fdn or connection_set_fdevent_interest() */
|
|
|
|
int rdhup = 0;
|
|
|
|
int n = fdevent_fdnode_interest(oneshot_fdn);
|
|
|
|
if (revents & FDEVENT_IN)
|
|
|
|
n &= ~FDEVENT_IN;
|
|
|
|
request_st * const r = &con->request;
|
|
|
|
if (r->state != CON_STATE_ERROR && (revents & (FDEVENT_HUP|FDEVENT_RDHUP))){
|
|
|
|
revents &= ~(FDEVENT_HUP|FDEVENT_RDHUP);
|
|
|
|
/* copied and modified from connection_handle_fdevent()
|
|
|
|
* fdevent_is_tcp_half_closed() will fail on pipe
|
|
|
|
* and, besides, read end of pipes should treat POLLHUP as POLLRDHUP */
|
|
|
|
n &= ~(FDEVENT_IN|FDEVENT_RDHUP);
|
|
|
|
rdhup = 1;
|
|
|
|
}
|
|
|
|
fdevent_fdnode_event_set(con->srv->ev, oneshot_fdn, n);
|
|
|
|
|
|
|
|
fdnode * const fdn = con->fdn; /* fdn->ctx == con */
|
|
|
|
handler_t rc = (fdn && (fdevent_handler)NULL != fdn->handler)
|
|
|
|
? (*fdn->handler)(con, revents)
|
|
|
|
: HANDLER_FINISHED;
|
|
|
|
|
|
|
|
if (rdhup) {
|
|
|
|
r->conf.stream_request_body &=
|
|
|
|
~(FDEVENT_STREAM_REQUEST_BUFMIN|FDEVENT_STREAM_REQUEST_POLLIN);
|
|
|
|
r->conf.stream_request_body |= FDEVENT_STREAM_REQUEST_POLLRDHUP;
|
|
|
|
r->conf.stream_request_body |= FDEVENT_STREAM_REQUEST_TCP_FIN;
|
|
|
|
con->is_readable = 1; /*(can read 0 for end-of-stream)*/
|
|
|
|
if (chunkqueue_is_empty(con->read_queue)) r->keep_alive = 0;
|
|
|
|
if (r->reqbody_length < -1) /*(transparent proxy mode; no more data)*/
|
|
|
|
r->reqbody_length = r->reqbody_queue.bytes_in;
|
|
|
|
}
|
|
|
|
|
|
|
|
return rc;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
__attribute_cold__
|
|
|
|
static int server_oneshot_init_pipe(server *srv, int fdin, int fdout) {
|
|
|
|
/* Note: attempt to work with netcat pipes though other code expects socket.
|
|
|
|
* netcat has different fds (pipes) for stdin and stdout. To support
|
|
|
|
* netcat, need to avoid S_ISSOCK(), getsockname(), and getpeername(),
|
|
|
|
* reconstructing addresses from environment variables:
|
|
|
|
* NCAT_LOCAL_ADDR NCAT_LOCAL_PORT
|
|
|
|
* NCAT_REMOTE_ADDR NCAT_REMOTE_PORT
|
|
|
|
* NCAT_PROTO (TCP, UDP, SCTP)
|
|
|
|
*/
|
|
|
|
connection *con;
|
|
|
|
const server_socket *srv_socket;
|
|
|
|
sock_addr cnt_addr;
|
|
|
|
|
|
|
|
/* detect if called from netcat or else fabricate localhost addrs */
|
|
|
|
const char * const ncat =
|
|
|
|
getenv("NCAT_LOCAL_ADDR");
|
|
|
|
const char * const ncat_local_addr =
|
|
|
|
ncat ? ncat : "127.0.0.1"; /*(fabricated)*/
|
|
|
|
const char * const ncat_local_port =
|
|
|
|
ncat ? getenv("NCAT_LOCAL_PORT") : "80"; /*(fabricated)*/
|
|
|
|
const char * const ncat_remote_addr =
|
|
|
|
ncat ? getenv("NCAT_REMOTE_ADDR") : "127.0.0.1"; /*(fabricated)*/
|
|
|
|
const char * const ncat_remote_port =
|
|
|
|
ncat ? getenv("NCAT_REMOTE_PORT") : "48080"; /*(fabricated)*/
|
|
|
|
if (NULL == ncat_local_addr || NULL == ncat_local_port) return 0;
|
|
|
|
if (NULL == ncat_remote_addr || NULL == ncat_remote_port) return 0;
|
|
|
|
|
|
|
|
const int family = ncat && strchr(ncat_local_addr,':') ? AF_INET6 : AF_INET;
|
|
|
|
unsigned short port;
|
|
|
|
|
|
|
|
port = (unsigned short)strtol(ncat_local_port, NULL, 10);
|
|
|
|
if (1 != sock_addr_inet_pton(&cnt_addr, ncat_local_addr, family, port)) {
|
|
|
|
log_error(srv->errh, __FILE__, __LINE__, "invalid local addr");
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
srv_socket = server_oneshot_getsock(srv, &cnt_addr);
|
|
|
|
if (NULL == srv_socket) return 0;
|
|
|
|
|
|
|
|
port = (unsigned short)strtol(ncat_remote_port, NULL, 10);
|
|
|
|
if (1 != sock_addr_inet_pton(&cnt_addr, ncat_remote_addr, family, port)) {
|
|
|
|
log_error(srv->errh, __FILE__, __LINE__, "invalid remote addr");
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*(must set flags; fd did not pass through fdevent accept() logic)*/
|
|
|
|
if (-1 == fdevent_fcntl_set_nb_cloexec(fdin)) {
|
|
|
|
log_perror(srv->errh, __FILE__, __LINE__, "fcntl()");
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
if (-1 == fdevent_fcntl_set_nb_cloexec(fdout)) {
|
|
|
|
log_perror(srv->errh, __FILE__, __LINE__, "fcntl()");
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
con = connection_accepted(srv, srv_socket, &cnt_addr, fdout);
|
|
|
|
if (NULL == con) return 0;
|
|
|
|
|
|
|
|
/* note: existing routines assume socket, not pipe
|
|
|
|
* connections.c:connection_read_cq()
|
|
|
|
* uses recv() ifdef __WIN32
|
|
|
|
* passes S_IFSOCK to fdevent_ioctl_fionread()
|
|
|
|
* (The routine could be copied and modified, if required)
|
|
|
|
* This is unlikely to work if TLS is used over pipe since the SSL_CTX
|
|
|
|
* is associated with the other end of the pipe. However, if using
|
|
|
|
* pipes, using TLS is unexpected behavior.
|
|
|
|
*/
|
|
|
|
|
|
|
|
/*assert(oneshot_fd == fdin);*/
|
|
|
|
oneshot_read_cq = con->network_read;
|
|
|
|
con->network_read = server_oneshot_read_cq;
|
|
|
|
oneshot_fdn =
|
|
|
|
fdevent_register(srv->ev, fdin, server_oneshot_handle_fdevent, con);
|
|
|
|
fdevent_fdnode_event_set(srv->ev, oneshot_fdn, FDEVENT_RDHUP);
|
|
|
|
|
|
|
|
connection_state_machine(con);
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
__attribute_cold__
|
|
|
|
static int server_oneshot_init(server *srv, int fd) {
|
|
|
|
connection *con;
|
|
|
|
const server_socket *srv_socket;
|
|
|
|
sock_addr cnt_addr;
|
|
|
|
socklen_t cnt_len;
|
|
|
|
|
|
|
|
cnt_len = sizeof(cnt_addr);
|
|
|
|
if (0 != getsockname(fd, (struct sockaddr *)&cnt_addr, &cnt_len)) {
|
|
|
|
log_perror(srv->errh, __FILE__, __LINE__, "getsockname()");
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
srv_socket = server_oneshot_getsock(srv, &cnt_addr);
|
|
|
|
if (NULL == srv_socket) return 0;
|
|
|
|
|
|
|
|
#ifdef __clang_analyzer__
|
|
|
|
memset(&cnt_addr, 0, sizeof(cnt_addr));
|
|
|
|
#endif
|
|
|
|
cnt_len = sizeof(cnt_addr);
|
|
|
|
if (0 != getpeername(fd, (struct sockaddr *)&cnt_addr, &cnt_len)) {
|
|
|
|
log_perror(srv->errh, __FILE__, __LINE__, "getpeername()");
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*(must set flags; fd did not pass through fdevent accept() logic)*/
|
|
|
|
if (-1 == fdevent_fcntl_set_nb_cloexec(fd)) {
|
|
|
|
log_perror(srv->errh, __FILE__, __LINE__, "fcntl()");
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (sock_addr_get_family(&cnt_addr) != AF_UNIX) {
|
|
|
|
network_accept_tcp_nagle_disable(fd);
|
|
|
|
}
|
|
|
|
|
|
|
|
con = connection_accepted(srv, srv_socket, &cnt_addr, fd);
|
|
|
|
if (NULL == con) return 0;
|
|
|
|
|
|
|
|
connection_state_machine(con);
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
__attribute_cold__
|
|
|
|
static void show_version (void) {
|
|
|
|
char *b = PACKAGE_DESC TEXT_SSL \
|
|
|
|
" - a light and fast webserver\n"
|
|
|
|
#ifdef NONREPRODUCIBLE_BUILD
|
|