Ejemplo n.º 1
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(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);
    }
}
Ejemplo n.º 2
0
Archivo: lwan.c Proyecto: lpereira/lwan
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;
    }
}
Ejemplo n.º 3
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);
        }
    }
}
Ejemplo n.º 4
0
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;
        }
    }
}
Ejemplo n.º 5
0
Archivo: lwan.c Proyecto: lpereira/lwan
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);
}
Ejemplo n.º 6
0
Archivo: lwan.c Proyecto: diviaki/lwan
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;
}
Ejemplo n.º 7
0
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;
}
Ejemplo n.º 8
0
Archivo: lwan.c Proyecto: diviaki/lwan
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();
}
Ejemplo n.º 9
0
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();
}
Ejemplo n.º 10
0
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;
}
Ejemplo n.º 11
0
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;
}
Ejemplo n.º 12
0
Archivo: lwan.c Proyecto: diviaki/lwan
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);
    }
}
Ejemplo n.º 13
0
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();
}
Ejemplo n.º 14
0
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);
}
Ejemplo n.º 15
0
Archivo: lwan.c Proyecto: diviaki/lwan
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);
}
Ejemplo n.º 16
0
Archivo: lwan.c Proyecto: diviaki/lwan
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();
}
Ejemplo n.º 17
0
Archivo: lwan.c Proyecto: lpereira/lwan
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);
}
Ejemplo n.º 18
0
Archivo: lwan.c Proyecto: lpereira/lwan
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;
}
Ejemplo n.º 19
0
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;
}