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(); }
static void allocate_connections(lwan_t *l, size_t max_open_files) { l->conns = calloc(max_open_files, sizeof(lwan_connection_t)); if (!l->conns) lwan_status_critical_perror("calloc"); }
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 void allocate_connections(struct lwan *l, size_t max_open_files) { const size_t sz = max_open_files * sizeof(struct lwan_connection); if (posix_memalign((void **)&l->conns, 64, align_to_size(sz, 64))) lwan_status_critical_perror("aligned_alloc"); memset(l->conns, 0, sz); }
static rlim_t setup_open_file_count_limits(void) { struct rlimit r; if (getrlimit(RLIMIT_NOFILE, &r) < 0) lwan_status_critical_perror("getrlimit"); if (r.rlim_max != r.rlim_cur) { if (r.rlim_max == RLIM_INFINITY) r.rlim_cur = OPEN_MAX; 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"); } return r.rlim_cur; }
static void allocate_connections(lwan_t *l, size_t max_open_files) { #if defined(_ISOC11_SOURCE) const size_t sz = max_open_files * sizeof(lwan_connection_t); l->conns = aligned_alloc(64, sz); if (!l->conns) lwan_status_critical_perror("aligned_alloc"); memset(l->conns, 0, sz); #else l->conns = calloc(max_open_files, sizeof(lwan_connection_t)); if (!l->conns) lwan_status_critical_perror("calloc"); #endif }
static struct lwan_url_map *add_url_map(struct lwan_trie *t, const char *prefix, const struct lwan_url_map *map) { struct lwan_url_map *copy = malloc(sizeof(*copy)); if (!copy) lwan_status_critical_perror("Could not copy URL map"); memcpy(copy, map, sizeof(*copy)); copy->prefix = strdup(prefix ? prefix : copy->prefix); if (!copy->prefix) lwan_status_critical_perror("Could not copy URL prefix"); copy->prefix_len = strlen(copy->prefix); lwan_trie_add(t, copy->prefix, copy); return copy; }
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"); }
void lwan_trie_add(struct lwan_trie *trie, const char *key, void *data) { if (UNLIKELY(!trie || !key || !data)) return; struct lwan_trie_node **knode, *node; const char *orig_key = key; /* Traverse the trie, allocating nodes if necessary */ for (knode = &trie->root; *key; knode = &node->next[(int)(*key++ & 7)]) GET_NODE(); /* Get the leaf node (allocate it if necessary) */ GET_NODE(); struct lwan_trie_leaf *leaf = find_leaf_with_key(node, orig_key, (size_t)(key - orig_key)); bool had_key = leaf; if (!leaf) { leaf = malloc(sizeof(*leaf)); if (!leaf) lwan_status_critical_perror("malloc"); } else if (trie->free_node) { trie->free_node(leaf->data); } leaf->data = data; if (!had_key) { leaf->key = strdup(orig_key); leaf->next = node->leaf; node->leaf = leaf; } return; oom: lwan_status_critical_perror("calloc"); }
static void create_thread(lwan_t *l, lwan_thread_t *thread) { pthread_attr_t attr; memset(thread, 0, sizeof(*thread)); thread->lwan = l; if ((thread->epoll_fd = epoll_create1(EPOLL_CLOEXEC)) < 0) lwan_status_critical_perror("epoll_create"); if (pthread_attr_init(&attr)) lwan_status_critical_perror("pthread_attr_init"); if (pthread_attr_setscope(&attr, PTHREAD_SCOPE_SYSTEM)) lwan_status_critical_perror("pthread_attr_setscope"); if (pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE)) lwan_status_critical_perror("pthread_attr_setdetachstate"); if (pipe2(thread->pipe_fd, O_NONBLOCK | O_CLOEXEC) < 0) lwan_status_critical_perror("pipe"); struct epoll_event event = { .events = EPOLLIN, .data.ptr = NULL }; if (epoll_ctl(thread->epoll_fd, EPOLL_CTL_ADD, thread->pipe_fd[0], &event) < 0) lwan_status_critical_perror("epoll_ctl"); if (pthread_create(&thread->self, &attr, thread_io_loop, thread)) lwan_status_critical_perror("pthread_create"); if (pthread_attr_destroy(&attr)) lwan_status_critical_perror("pthread_attr_destroy"); } void lwan_thread_add_client(lwan_thread_t *t, int fd) { t->lwan->conns[fd].flags = 0; t->lwan->conns[fd].thread = t; if (UNLIKELY(write(t->pipe_fd[1], &fd, sizeof(int)) < 0)) lwan_status_perror("write"); }
void lwan_set_url_map(struct lwan *l, const struct lwan_url_map *map) { lwan_trie_destroy(&l->url_map_trie); if (UNLIKELY(!lwan_trie_init(&l->url_map_trie, destroy_urlmap))) lwan_status_critical_perror("Could not initialize trie"); for (; map->prefix; map++) { struct lwan_url_map *copy = add_url_map(&l->url_map_trie, NULL, map); if (copy->module && copy->module->create) { copy->data = copy->module->create (map->prefix, copy->args); copy->flags = copy->module->flags; copy->handler = copy->module->handle_request; } else { copy->flags = HANDLER_PARSE_MASK; } } }
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_set_url_map(lwan_t *l, const lwan_url_map_t *map) { lwan_trie_destroy(&l->url_map_trie); if (UNLIKELY(!lwan_trie_init(&l->url_map_trie, destroy_urlmap))) lwan_status_critical_perror("Could not initialize trie"); for (; map->prefix; map++) { lwan_url_map_t *copy = add_url_map(&l->url_map_trie, NULL, map); if (UNLIKELY(!copy)) continue; if (copy->module && copy->module->init) { copy->data = copy->module->init(copy->args); copy->flags = copy->module->flags; copy->handler = copy->module->handle; } else { copy->flags = HANDLER_PARSE_MASK; } } }
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); }
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))) { lwan_status_perror("read"); 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)) lwan_status_critical_perror("epoll_ctl"); 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); lwan_connection_t *conns = t->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, conns, t->lwan->config.keep_alive_timeout); 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_or_reset_coro_if_needed(conn, &switcher, &dq); } else { conn = ep_event->data.ptr; if (UNLIKELY(ep_event->events & (EPOLLRDHUP | EPOLLHUP))) { destroy_coro(&dq, conn); continue; } spawn_or_reset_coro_if_needed(conn, &switcher, &dq); resume_coro_if_needed(&dq, conn, epoll_fd); } death_queue_move_to_last(&dq, conn); } } } epoll_fd_closed: free(events); return NULL; }
static void lwan_fd_watch_init(struct lwan *l) { l->epfd = epoll_create1(EPOLL_CLOEXEC); if (l->epfd < 0) lwan_status_critical_perror("epoll_create1"); }