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_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(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 void lwan_module_register(lwan_t *l, const lwan_module_t *module) { if (!module->name) lwan_status_critical("Module at %p has no name", module); lwan_status_debug("Registering module \"%s\"", module->name); hash_add(l->module_registry, module->name, module); }
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; }
void lwan_thread_init(lwan_t *l) { lwan_status_debug("Initializing threads"); l->thread.threads = calloc((size_t)l->thread.count, sizeof(lwan_thread_t)); if (!l->thread.threads) lwan_status_critical("Could not allocate memory for threads"); for (short i = 0; i < l->thread.count; i++) create_thread(l, &l->thread.threads[i]); }
static void try_setup_from_config(struct lwan *l, const struct lwan_config *config) { if (!setup_from_config(l, config->config_file_path)) { if (config->config_file_path) { lwan_status_critical("Could not read config file: %s", config->config_file_path); } } /* `quiet` key might have changed value. */ lwan_status_init(l); }
void lwan_straitjacket_enforce(config_t *c, config_line_t *l) { char *user_name = NULL; char *chroot_path = NULL; uid_t uid; gid_t gid; if (geteuid() != 0) { config_error(c, "Straitjacket requires root privileges"); return; } while (config_read_line(c, l)) { switch (l->type) { case CONFIG_LINE_TYPE_LINE: /* TODO: limit_syscalls */ if (!strcmp(l->line.key, "user")) { user_name = strdupa(l->line.value); } else if (!strcmp(l->line.key, "chroot")) { chroot_path = strdupa(l->line.value); } else { config_error(c, "Invalid key: %s", l->line.key); return; } break; case CONFIG_LINE_TYPE_SECTION: config_error(c, "Straitjacket accepts no sections"); return; case CONFIG_LINE_TYPE_SECTION_END: if (!get_user_uid_gid(user_name, &uid, &gid)) { config_error(c, "Unknown user: %s", user_name); return; } if (chroot_path) { if (chroot(chroot_path) < 0) { lwan_status_critical_perror("Could not chroot() to %s", chroot_path); } lwan_status_debug("Jailed to %s", chroot_path); } if (!switch_to_user(uid, gid, user_name)) { lwan_status_critical("Could not drop privileges to %s, aborting", user_name); } return; } } config_error(c, "Expecting section end while parsing straitjacket"); }
static int setup_socket_from_systemd(void) { int fd = SD_LISTEN_FDS_START; if (!sd_is_socket_inet(fd, AF_UNSPEC, SOCK_STREAM, 1, 0)) lwan_status_critical("Passed file descriptor is not a " "listening TCP socket"); int flags = fcntl(fd, F_GETFD); if (flags < 0) lwan_status_critical_perror("Could not obtain socket flags"); if (fcntl(fd, F_SETFD, flags | FD_CLOEXEC) < 0) lwan_status_critical_perror("Could not set socket flags"); return fd; }
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; }
static lwan_connection_t * grab_and_watch_client(int epoll_fd, int pipe_fd, lwan_connection_t *conns) { int fd; if (UNLIKELY(read(pipe_fd, &fd, sizeof(int)) != sizeof(int))) return NULL; struct epoll_event event = { .events = events_by_write_flag[1], .data.ptr = &conns[fd] }; if (UNLIKELY(epoll_ctl(epoll_fd, EPOLL_CTL_ADD, fd, &event) < 0)) return NULL; return &conns[fd]; } static void * thread_io_loop(void *data) { lwan_thread_t *t = data; const int epoll_fd = t->epoll_fd; const int read_pipe_fd = t->pipe_fd[0]; const int max_events = min((int)t->lwan->thread.max_fd, 1024); const lwan_t *lwan = t->lwan; lwan_connection_t *conns = lwan->conns; struct epoll_event *events; coro_switcher_t switcher; struct death_queue_t dq; int n_fds; lwan_status_debug("Starting IO loop on thread #%d", (unsigned short)(ptrdiff_t)(t - t->lwan->thread.threads) + 1); events = calloc((size_t)max_events, sizeof(*events)); if (UNLIKELY(!events)) lwan_status_critical("Could not allocate memory for events"); death_queue_init(&dq, lwan); for (;;) { switch (n_fds = epoll_wait(epoll_fd, events, max_events, death_queue_epoll_timeout(&dq))) { case -1: switch (errno) { case EBADF: case EINVAL: goto epoll_fd_closed; } continue; case 0: /* timeout: shutdown waiting sockets */ death_queue_kill_waiting(&dq); break; default: /* activity in some of this poller's file descriptor */ update_date_cache(t); for (struct epoll_event *ep_event = events; n_fds--; ep_event++) { lwan_connection_t *conn; if (!ep_event->data.ptr) { conn = grab_and_watch_client(epoll_fd, read_pipe_fd, conns); if (UNLIKELY(!conn)) continue; spawn_coro(conn, &switcher, &dq); } else { conn = ep_event->data.ptr; if (UNLIKELY(ep_event->events & (EPOLLRDHUP | EPOLLHUP))) { destroy_coro(&dq, conn); continue; } resume_coro_if_needed(&dq, conn, epoll_fd); } death_queue_move_to_last(&dq, conn); } } } epoll_fd_closed: death_queue_kill_all(&dq); free(events); return NULL; }
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; }