void lwan_main_loop(lwan_t *l) { assert(main_socket == -1); main_socket = l->main_socket; if (signal(SIGINT, sigint_handler) == SIG_ERR) lwan_status_critical("Could not set signal handler"); lwan_status_info("Ready to serve"); for (;;) { int client_fd = accept4(main_socket, NULL, NULL, SOCK_NONBLOCK | SOCK_CLOEXEC); if (UNLIKELY(client_fd < 0)) { if (errno != EBADF) { lwan_status_perror("accept"); continue; } if (main_socket < 0) { lwan_status_info("Signal 2 (Interrupt) received"); } else { lwan_status_info("Main socket closed for unknown reasons"); } break; } schedule_client(l, client_fd); } }
static ALWAYS_INLINE enum herd_accept accept_one(struct lwan *l, uint64_t *cores) { int fd = accept4((int)main_socket, NULL, NULL, SOCK_NONBLOCK | SOCK_CLOEXEC); if (LIKELY(fd >= 0)) { *cores |= UINT64_C(1)<<schedule_client(l, fd); return HERD_MORE; } switch (errno) { case EAGAIN: return HERD_GONE; case EBADF: case ECONNABORTED: case EINVAL: if (main_socket < 0) { lwan_status_info("Signal 2 (Interrupt) received"); } else { lwan_status_info("Main socket closed for unknown reasons"); } return HERD_SHUTDOWN; default: lwan_status_perror("accept"); return HERD_MORE; } }
void lwan_main_loop(lwan_t *l) { assert(main_socket == -1); main_socket = l->main_socket; if (signal(SIGINT, sigint_handler) == SIG_ERR) lwan_status_critical("Could not set signal handler"); lwan_status_info("Ready to serve"); for (;;) { int client_fd = accept4((int)main_socket, NULL, NULL, SOCK_NONBLOCK | SOCK_CLOEXEC); if (UNLIKELY(client_fd < 0)) { switch (errno) { case EBADF: case ECONNABORTED: if (main_socket < 0) { lwan_status_info("Signal 2 (Interrupt) received"); } else { lwan_status_info("Main socket closed for unknown reasons"); } return; } lwan_status_perror("accept"); } else { schedule_client(l, client_fd); } } }
void lwan_main_loop(struct lwan *l) { uint64_t cores = 0; assert(main_socket == -1); main_socket = l->main_socket; if (signal(SIGINT, sigint_handler) == SIG_ERR) lwan_status_critical("Could not set signal handler"); lwan_status_info("Ready to serve"); while (LIKELY(main_socket >= 0)) { if (UNLIKELY(accept_herd(l, &cores) == HERD_SHUTDOWN)) break; if (LIKELY(cores)) { for (unsigned short t = 0; t < l->thread.count; t++) { if (cores & 1ULL<<t) lwan_thread_nudge(&l->thread.threads[t]); } cores = 0; } } }
void lwan_init_with_config(struct lwan *l, const struct lwan_config *config) { /* Load defaults */ memset(l, 0, sizeof(*l)); memcpy(&l->config, config, sizeof(*config)); l->config.listener = dup_or_null(l->config.listener); l->config.config_file_path = dup_or_null(l->config.config_file_path); /* Initialize status first, as it is used by other things during * their initialization. */ lwan_status_init(l); /* These will only print debugging messages. Debug messages are always * printed if we're on a debug build, so the quiet setting will be * respected. */ lwan_job_thread_init(); lwan_tables_init(); try_setup_from_config(l, config); lwan_response_init(l); /* Continue initialization as normal. */ lwan_status_debug("Initializing lwan web server"); l->n_cpus = get_number_of_cpus(); if (!l->config.n_threads) { l->thread.count = l->n_cpus; if (l->thread.count == 1) l->thread.count = 2; } else if (l->config.n_threads > 3 * l->n_cpus) { l->thread.count = (short unsigned int)(l->n_cpus * 3); lwan_status_warning("%d threads requested, but only %d online CPUs; " "capping to %d threads", l->config.n_threads, l->n_cpus, 3 * l->n_cpus); } else if (l->config.n_threads > 63) { l->thread.count = 64; lwan_status_warning("%d threads requested, but max 64 supported", l->config.n_threads); } else { l->thread.count = l->config.n_threads; } rlim_t max_open_files = setup_open_file_count_limits(); allocate_connections(l, (size_t)max_open_files); l->thread.max_fd = (unsigned)max_open_files / (unsigned)l->thread.count; lwan_status_info("Using %d threads, maximum %d sockets per thread", l->thread.count, l->thread.max_fd); signal(SIGPIPE, SIG_IGN); lwan_readahead_init(); lwan_thread_init(l); lwan_socket_init(l); lwan_http_authorize_init(); lwan_fd_watch_init(l); }
static bool setup_from_config(lwan_t *lwan) { config_t conf; config_line_t line; bool has_listener = false; char path_buf[PATH_MAX]; const char *path; path = get_config_path(path_buf); lwan_status_info("Loading configuration file: %s", path); lwan->url_map_trie = lwan_trie_new(destroy_urlmap); if (!config_open(&conf, path)) return false; while (config_read_line(&conf, &line)) { switch (line.type) { case CONFIG_LINE_TYPE_LINE: if (!strcmp(line.line.key, "keep_alive_timeout")) lwan->config.keep_alive_timeout = parse_int(line.line.value, default_config.keep_alive_timeout); else if (!strcmp(line.line.key, "quiet")) lwan->config.quiet = parse_bool(line.line.value, default_config.quiet); else if (!strcmp(line.line.key, "reuse_port")) lwan->config.reuse_port = parse_bool(line.line.value, default_config.reuse_port); else config_error(&conf, "Unknown config key: %s", line.line.key); break; case CONFIG_LINE_TYPE_SECTION: if (!has_listener) { has_listener = true; if (!strcmp(line.section.name, "listener")) parse_listener(&conf, &line, lwan); else config_error(&conf, "Unknown section type: %s", line.section.name); } else { config_error(&conf, "Only one listener supported"); } break; case CONFIG_LINE_TYPE_SECTION_END: config_error(&conf, "Unexpected section end"); } } if (conf.error_message) { lwan_status_critical("Error on config file \"%s\", line %d: %s", path, conf.line, conf.error_message); } config_close(&conf); return true; }
static int listen_addrinfo(int fd, const struct addrinfo *addr) { if (listen(fd, get_backlog_size()) < 0) lwan_status_critical_perror("listen"); char host_buf[NI_MAXHOST], serv_buf[NI_MAXSERV]; int ret = getnameinfo(addr->ai_addr, addr->ai_addrlen, host_buf, sizeof(host_buf), serv_buf, sizeof(serv_buf), NI_NUMERICHOST | NI_NUMERICSERV); if (ret) lwan_status_critical("getnameinfo: %s", gai_strerror(ret)); if (addr->ai_family == AF_INET6) lwan_status_info("Listening on http://[%s]:%s", host_buf, serv_buf); else lwan_status_info("Listening on http://%s:%s", host_buf, serv_buf); return fd; }
void lwan_init(lwan_t *l) { int max_threads = sysconf(_SC_NPROCESSORS_ONLN); struct rlimit r; /* Load defaults */ memcpy(&l->config, &default_config, sizeof(default_config)); /* Initialize status first, as it is used by other things during * their initialization. */ lwan_status_init(l); /* These will only print debugging messages. Debug messages are always * printed if we're on a debug build, so the quiet setting will be * respected. */ lwan_job_thread_init(); lwan_response_init(); lwan_tables_init(); /* Load the configuration file. */ if (!setup_from_config(l)) lwan_status_warning("Could not read config file, using defaults"); /* Continue initialization as normal. */ lwan_status_debug("Initializing lwan web server"); l->thread.count = max_threads > 0 ? max_threads : 2; if (getrlimit(RLIMIT_NOFILE, &r) < 0) lwan_status_critical_perror("getrlimit"); if (r.rlim_max == RLIM_INFINITY) r.rlim_cur *= 8; else if (r.rlim_cur < r.rlim_max) r.rlim_cur = r.rlim_max; if (setrlimit(RLIMIT_NOFILE, &r) < 0) lwan_status_critical_perror("setrlimit"); l->conns = calloc(r.rlim_cur, sizeof(lwan_connection_t)); l->thread.max_fd = r.rlim_cur / l->thread.count; lwan_status_info("Using %d threads, maximum %d sockets per thread", l->thread.count, l->thread.max_fd); for (--r.rlim_cur; r.rlim_cur; --r.rlim_cur) l->conns[r.rlim_cur].response_buffer = strbuf_new(); srand(time(NULL)); signal(SIGPIPE, SIG_IGN); close(STDIN_FILENO); lwan_thread_init(l); lwan_socket_init(l); lwan_http_authorize_init(); }
void lwan_init_with_config(lwan_t *l, const lwan_config_t *config) { /* Load defaults */ memset(l, 0, sizeof(*l)); memcpy(&l->config, config, sizeof(*config)); /* Initialize status first, as it is used by other things during * their initialization. */ lwan_status_init(l); /* These will only print debugging messages. Debug messages are always * printed if we're on a debug build, so the quiet setting will be * respected. */ lwan_job_thread_init(); lwan_tables_init(); lwan_module_init(l); /* Load the configuration file. */ if (config == &default_config) { if (!setup_from_config(l)) lwan_status_warning("Could not read config file, using defaults"); /* `quiet` key might have changed value. */ lwan_status_init(l); } lwan_response_init(l); /* Continue initialization as normal. */ lwan_status_debug("Initializing lwan web server"); if (!l->config.n_threads) { l->thread.count = get_number_of_cpus(); if (l->thread.count == 1) l->thread.count = 2; } else { l->thread.count = l->config.n_threads; } rlim_t max_open_files = setup_open_file_count_limits(); allocate_connections(l, (size_t)max_open_files); l->thread.max_fd = (unsigned)max_open_files / (unsigned)l->thread.count; lwan_status_info("Using %d threads, maximum %d sockets per thread", l->thread.count, l->thread.max_fd); signal(SIGPIPE, SIG_IGN); lwan_thread_init(l); lwan_socket_init(l); lwan_http_authorize_init(); }
static bool wait_herd(void) { struct pollfd fds = { .fd = (int)main_socket, .events = POLLIN }; return poll(&fds, 1, -1) == 1; } enum herd_accept { HERD_MORE = 0, HERD_GONE = -1, HERD_SHUTDOWN = 1 }; static enum herd_accept accept_one(struct lwan *l, uint64_t *cores) { int fd = accept4((int)main_socket, NULL, NULL, SOCK_NONBLOCK | SOCK_CLOEXEC); if (LIKELY(fd >= 0)) { *cores |= 1ULL<<(unsigned)schedule_client(l, fd); return HERD_MORE; } switch (errno) { case EAGAIN: return HERD_GONE; case EBADF: case ECONNABORTED: case EINVAL: if (main_socket < 0) { lwan_status_info("Signal 2 (Interrupt) received"); } else { lwan_status_info("Main socket closed for unknown reasons"); } return HERD_SHUTDOWN; } lwan_status_perror("accept"); return HERD_MORE; }
static bool switch_to_user(uid_t uid, gid_t gid, const char *username) { lwan_status_info("Dropping privileges to UID %d, GID %d (%s)", uid, gid, username); if (setgid(gid) < 0) return false; #if defined(__APPLE__) if (initgroups(username, (int)gid) < 0) return false; #else if (initgroups(username, gid) < 0) return false; #endif if (setuid(uid) < 0) return false; return getegid() == gid && geteuid() == uid; }
void lwan_main_loop(lwan_t *l) { if (setjmp(cleanup_jmp_buf)) return; signal(SIGINT, _signal_handler); lwan_status_info("Ready to serve"); for (;;) { int child_fd = accept4(l->main_socket, NULL, NULL, SOCK_NONBLOCK); if (UNLIKELY(child_fd < 0)) { lwan_status_perror("accept"); continue; } _push_request_fd(l, child_fd); } }
void lwan_shutdown(struct lwan *l) { lwan_status_info("Shutting down"); free(l->config.listener); free(l->config.error_template); free(l->config.config_file_path); lwan_job_thread_shutdown(); lwan_thread_shutdown(l); lwan_status_debug("Shutting down URL handlers"); lwan_trie_destroy(&l->url_map_trie); free(l->conns); lwan_response_shutdown(l); lwan_tables_shutdown(); lwan_status_shutdown(l); lwan_http_authorize_shutdown(); }
void lwan_shutdown(lwan_t *l) { lwan_status_info("Shutting down"); if (l->config.listener != default_config.listener) free(l->config.listener); lwan_job_thread_shutdown(); lwan_thread_shutdown(l); lwan_status_debug("Shutting down URL handlers"); lwan_trie_destroy(&l->url_map_trie); free(l->conns); lwan_response_shutdown(); lwan_tables_shutdown(); lwan_status_shutdown(l); lwan_http_authorize_shutdown(); lwan_module_shutdown(l); }
static ALWAYS_INLINE void _push_request_fd(lwan_t *l, int fd) { unsigned thread; #ifdef __x86_64__ assert(sizeof(lwan_connection_t) == 32); /* Since lwan_connection_t is guaranteed to be 32-byte long, two of them * can fill up a cache line. This formula will group two connections * per thread in a way that false-sharing is avoided. This gives wrong * results when fd=0, but this shouldn't happen (as 0 is either the * standard input or the main socket, but even if that changes, * scheduling will still work). */ thread = ((fd - 1) / 2) % l->thread.count; #else static int counter = 0; thread = counter++ % l->thread.count; #endif int epoll_fd = l->thread.threads[thread].epoll_fd; struct epoll_event event = { .events = EPOLLIN | EPOLLRDHUP | EPOLLERR | EPOLLET, .data.ptr = &l->conns[fd] }; l->conns[fd].flags = 0; l->conns[fd].thread = &l->thread.threads[thread]; if (UNLIKELY(epoll_ctl(epoll_fd, EPOLL_CTL_ADD, fd, &event) < 0)) lwan_status_critical_perror("epoll_ctl"); } static void _signal_handler(int signal_number) { lwan_status_info("Signal %d (%s) received", signal_number, strsignal(signal_number)); longjmp(cleanup_jmp_buf, 1); }
void lwan_shutdown(lwan_t *l) { lwan_status_info("Shutting down"); lwan_job_thread_shutdown(); lwan_thread_shutdown(l); lwan_socket_shutdown(l); lwan_status_debug("Shutting down URL handlers"); lwan_trie_destroy(l->url_map_trie); int i; for (i = l->thread.max_fd * l->thread.count - 1; i >= 0; --i) strbuf_free(l->conns[i].response_buffer); free(l->conns); lwan_response_shutdown(); lwan_tables_shutdown(); lwan_status_shutdown(l); lwan_http_authorize_shutdown(); }
struct lwan_fd_watch *lwan_watch_fd(struct lwan *l, int fd, uint32_t events, coro_function_t coro_fn, void *data) { struct lwan_fd_watch *watch; watch = malloc(sizeof(*watch)); if (!watch) return NULL; watch->coro = coro_new(&l->switcher, coro_fn, data); if (!watch->coro) goto out; struct epoll_event ev = {.events = events, .data.ptr = watch->coro}; if (epoll_ctl(l->epfd, EPOLL_CTL_ADD, fd, &ev) < 0) { coro_free(watch->coro); goto out; } watch->fd = fd; return watch; out: free(watch); return NULL; } void lwan_unwatch_fd(struct lwan *l, struct lwan_fd_watch *w) { if (l->main_socket != w->fd) { if (epoll_ctl(l->epfd, EPOLL_CTL_DEL, w->fd, NULL) < 0) lwan_status_perror("Could not unwatch fd %d", w->fd); } coro_free(w->coro); free(w); } void lwan_main_loop(struct lwan *l) { struct epoll_event evs[16]; struct lwan_fd_watch *watch; assert(main_socket == -1); main_socket = l->main_socket; if (signal(SIGINT, sigint_handler) == SIG_ERR) lwan_status_critical("Could not set signal handler"); watch = lwan_watch_fd(l, l->main_socket, EPOLLIN | EPOLLHUP | EPOLLRDHUP, accept_connection_coro, l); if (!watch) lwan_status_critical("Could not watch main socket"); lwan_status_info("Ready to serve"); while (true) { int n_evs = epoll_wait(l->epfd, evs, N_ELEMENTS(evs), -1); if (UNLIKELY(n_evs < 0)) { if (main_socket < 0) break; if (errno == EINTR || errno == EAGAIN) continue; break; } for (int i = 0; i < n_evs; i++) { if (!coro_resume_value(evs[i].data.ptr, (int)evs[i].events)) break; } } lwan_unwatch_fd(l, watch); } #ifdef CLOCK_MONOTONIC_COARSE __attribute__((constructor)) static void detect_fastest_monotonic_clock(void) { struct timespec ts; if (!clock_gettime(CLOCK_MONOTONIC_COARSE, &ts)) monotonic_clock_id = CLOCK_MONOTONIC_COARSE; } #endif void lwan_set_thread_name(const char *name) { char thread_name[16]; char process_name[PATH_MAX]; char *tmp; int ret; if (proc_pidpath(getpid(), process_name, sizeof(process_name)) < 0) return; tmp = strrchr(process_name, '/'); if (!tmp) return; ret = snprintf(thread_name, sizeof(thread_name), "%s %s", tmp + 1, name); if (ret < 0) return; pthread_set_name_np(pthread_self(), thread_name); }
static bool setup_from_config(struct lwan *lwan, const char *path) { struct config *conf; struct config_line line; bool has_listener = false; char path_buf[PATH_MAX]; if (!path) path = lwan_get_config_path(path_buf, sizeof(path_buf)); lwan_status_info("Loading configuration file: %s", path); conf = config_open(path); if (!conf) return false; if (!lwan_trie_init(&lwan->url_map_trie, destroy_urlmap)) return false; while (config_read_line(conf, &line)) { switch (line.type) { case CONFIG_LINE_TYPE_LINE: if (streq(line.key, "keep_alive_timeout")) { lwan->config.keep_alive_timeout = (unsigned short)parse_long( line.value, default_config.keep_alive_timeout); } else if (streq(line.key, "quiet")) { lwan->config.quiet = parse_bool(line.value, default_config.quiet); } else if (streq(line.key, "reuse_port")) { lwan->config.reuse_port = parse_bool(line.value, default_config.reuse_port); } else if (streq(line.key, "proxy_protocol")) { lwan->config.proxy_protocol = parse_bool(line.value, default_config.proxy_protocol); } else if (streq(line.key, "allow_cors")) { lwan->config.allow_cors = parse_bool(line.value, default_config.allow_cors); } else if (streq(line.key, "expires")) { lwan->config.expires = parse_time_period(line.value, default_config.expires); } else if (streq(line.key, "error_template")) { free(lwan->config.error_template); lwan->config.error_template = strdup(line.value); } else if (streq(line.key, "threads")) { long n_threads = parse_long(line.value, default_config.n_threads); if (n_threads < 0) config_error(conf, "Invalid number of threads: %ld", n_threads); lwan->config.n_threads = (unsigned short int)n_threads; } else if (streq(line.key, "max_post_data_size")) { long max_post_data_size = parse_long( line.value, (long)default_config.max_post_data_size); if (max_post_data_size < 0) config_error(conf, "Negative maximum post data size"); else if (max_post_data_size > 128 * (1 << 20)) config_error(conf, "Maximum post data can't be over 128MiB"); lwan->config.max_post_data_size = (size_t)max_post_data_size; } else if (streq(line.key, "allow_temp_files")) { lwan->config.allow_post_temp_file = !!strstr(line.value, "post"); } else { config_error(conf, "Unknown config key: %s", line.key); } break; case CONFIG_LINE_TYPE_SECTION: if (streq(line.key, "listener")) { if (!has_listener) { parse_listener(conf, &line, lwan); has_listener = true; } else { config_error(conf, "Only one listener supported"); } } else if (streq(line.key, "straitjacket")) { lwan_straitjacket_enforce_from_config(conf); } else { config_error(conf, "Unknown section type: %s", line.key); } break; case CONFIG_LINE_TYPE_SECTION_END: config_error(conf, "Unexpected section end"); } } if (config_last_error(conf)) { lwan_status_critical("Error on config file \"%s\", line %d: %s", path, config_cur_line(conf), config_last_error(conf)); } config_close(conf); return true; }
static bool setup_from_config(lwan_t *lwan) { config_t conf; config_line_t line; bool has_listener = false; char path_buf[PATH_MAX]; const char *path; path = get_config_path(path_buf); lwan_status_info("Loading configuration file: %s", path); if (!lwan_trie_init(&lwan->url_map_trie, destroy_urlmap)) return false; if (!config_open(&conf, path)) return false; while (config_read_line(&conf, &line)) { switch (line.type) { case CONFIG_LINE_TYPE_LINE: if (!strcmp(line.line.key, "keep_alive_timeout")) lwan->config.keep_alive_timeout = (unsigned short)parse_long(line.line.value, default_config.keep_alive_timeout); else if (!strcmp(line.line.key, "quiet")) lwan->config.quiet = parse_bool(line.line.value, default_config.quiet); else if (!strcmp(line.line.key, "reuse_port")) lwan->config.reuse_port = parse_bool(line.line.value, default_config.reuse_port); else if (!strcmp(line.line.key, "proxy_protocol")) lwan->config.proxy_protocol = parse_bool(line.line.value, default_config.proxy_protocol); else if (!strcmp(line.line.key, "expires")) lwan->config.expires = parse_time_period(line.line.value, default_config.expires); else if (!strcmp(line.line.key, "error_template")) { free(lwan->config.error_template); lwan->config.error_template = strdup(line.line.value); } else if (!strcmp(line.line.key, "threads")) { long n_threads = parse_long(line.line.value, default_config.n_threads); if (n_threads < 0) config_error(&conf, "Invalid number of threads: %d", n_threads); lwan->config.n_threads = (unsigned short int)n_threads; } else config_error(&conf, "Unknown config key: %s", line.line.key); break; case CONFIG_LINE_TYPE_SECTION: if (!strcmp(line.section.name, "listener")) { if (!has_listener) { parse_listener(&conf, &line, lwan); has_listener = true; } else { config_error(&conf, "Only one listener supported"); } } else if (!strcmp(line.section.name, "straitjacket")) { lwan_straitjacket_enforce(&conf, &line); } else { config_error(&conf, "Unknown section type: %s", line.section.name); } break; case CONFIG_LINE_TYPE_SECTION_END: config_error(&conf, "Unexpected section end"); } } if (conf.error_message) { lwan_status_critical("Error on config file \"%s\", line %d: %s", path, conf.line, conf.error_message); } config_close(&conf); return true; }