void lwan_thread_shutdown(lwan_t *l) { lwan_status_debug("Shutting down threads"); for (int i = l->thread.count - 1; i >= 0; i--) { lwan_thread_t *t = &l->thread.threads[i]; char less_than_int = 0; lwan_status_debug("Closing epoll for thread %d (fd=%d)", i, t->epoll_fd); /* Close the epoll_fd and write less than an int to signal the * thread to gracefully finish. */ close(t->epoll_fd); write(t->pipe_fd[1], &less_than_int, sizeof(less_than_int)); } for (int i = l->thread.count - 1; i >= 0; i--) { lwan_thread_t *t = &l->thread.threads[i]; lwan_status_debug("Waiting for thread %d to finish", i); pthread_join(l->thread.threads[i].self, NULL); lwan_status_debug("Closing pipe (%d, %d)", t->pipe_fd[0], t->pipe_fd[1]); close(t->pipe_fd[0]); close(t->pipe_fd[1]); } free(l->thread.threads); }
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 void lwan_module_init(lwan_t *l) { if (!l->module_registry) { lwan_status_debug("Initializing module registry"); l->module_registry = hash_str_new(NULL, NULL); } }
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); }
void lwan_thread_shutdown(lwan_t *l) { lwan_status_debug("Shutting down threads"); for (int i = l->thread.count - 1; i >= 0; i--) { lwan_thread_t *t = &l->thread.threads[i]; char less_than_int = 0; ssize_t r; lwan_status_debug("Closing epoll for thread %d (fd=%d)", i, t->epoll_fd); /* Close the epoll_fd and write less than an int to signal the * thread to gracefully finish. */ close(t->epoll_fd); while (true) { r = write(t->pipe_fd[1], &less_than_int, sizeof(less_than_int)); if (r >= 0) break; if (errno == EAGAIN || errno == EWOULDBLOCK || errno == EINTR) continue; lwan_status_error("Could not write to I/O thread (%d) pipe to shutdown", i); break; } } for (int i = l->thread.count - 1; i >= 0; i--) { lwan_thread_t *t = &l->thread.threads[i]; lwan_status_debug("Waiting for thread %d to finish", i); pthread_join(l->thread.threads[i].self, NULL); lwan_status_debug("Closing pipe (%d, %d)", t->pipe_fd[0], t->pipe_fd[1]); close(t->pipe_fd[0]); close(t->pipe_fd[1]); } free(l->thread.threads); }
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(); }
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]); }
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_tables_init(void) { lwan_status_debug("Initializing tables"); if (mime_types) return; mime_types = hash_str_new(NULL, NULL); if (!mime_types) return; size_t i = 0; for (i = 0; i < N_ELEMENTS(mime_type_array); i++) hash_add(mime_types, mime_type_array[i].extension, mime_type_array[i].mime_type); }
void lwan_thread_shutdown(lwan_t *l) { lwan_status_debug("Shutting down threads"); for (int i = l->thread.count - 1; i >= 0; i--) { lwan_thread_t *t = &l->thread.threads[i]; /* Closing epoll_fd makes the thread gracefully finish. */ close(t->epoll_fd); close(t->pipe_fd[0]); close(t->pipe_fd[1]); pthread_tryjoin_np(l->thread.threads[i].self, NULL); } free(l->thread.threads); }
static const lwan_module_t *lwan_module_find(lwan_t *l, const char *name) { lwan_module_t *module = hash_find(l->module_registry, name); if (!module) { lwan_module_t *(*module_fn)(void); char module_symbol[128]; int r; for (const char *p = name; *p; p++) { if (isalnum(*p) || *p == '_') continue; lwan_status_error("Module name (%s) contains invalid character: %c", name, *p); return NULL; } r = snprintf(module_symbol, sizeof(module_symbol), "lwan_module_%s", name); if (r < 0 || r >= (int)sizeof(module_symbol)) { lwan_status_error("Module name too long: %s", name); return NULL; } module_fn = find_handler_symbol(module_symbol); if (!module_fn) { lwan_status_error("Module \"%s\" does not exist", name); return NULL; } module = module_fn(); if (!module) { lwan_status_error("Function \"%s()\" didn't return a module", module_symbol); return NULL; } lwan_status_debug("Module \"%s\" registered", name); hash_add(l->module_registry, module->name, module); } return module; }
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); }
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(); }
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; }
void lwan_tables_shutdown(void) { lwan_status_debug("Shutting down tables"); hash_free(mime_types); }