#include "common.hh" #include "args.hh" #include "config.hh" #include "inet.hh" #include "logger.hh" #include "looper.hh" #include "signal_handler.hh" #include "site.hh" #include "task_runner.hh" #include "transport.hh" #include "transport_fastcgi.hh" #include "transport_http.hh" #include "travel.hh" #include #include #include #include #include #include #include #include #include #ifndef VERSION # warning VERSION not set # define VERSION "" #endif namespace { class Server { public: ~Server() { for (auto& fd : listen_) looper_->remove(fd.get()); } bool setup(Logger* logger, Option const* config_arg, Option const* log_arg, std::function()> default_logger_factory) { auto config = Config::create(logger, config_arg->is_set() ? config_arg->arg() : "travel3.conf"); if (!config) return false; { std::filesystem::path log_file; if (log_arg->is_set()) { log_file = log_arg->arg(); } else { log_file = config->get_path("log_file", ""); } if (!log_file.empty()) { logger_ = Logger::create_file(log_file, logger); // If a log_file was requested and we can't append to it, treat as // fatal error instead of falling back to default. if (!logger_) return false; } else { logger_ = default_logger_factory(); } } // EPIPE is handled so SIGPIPE is no help. signal(SIGPIPE, SIG_IGN); looper_ = Looper::create(); runner_ = TaskRunner::create(looper_); travel_ = Travel::create(logger_, runner_); if (!travel_->setup(logger, config.get())) return false; site_ = Site::create(logger_, runner_, travel_); if (!site_->setup(logger, config.get())) return false; { std::map> transport_factories; transport_factories.emplace("http", create_transport_factory_http()); transport_factories.emplace("fastcgi", create_transport_factory_fastcgi()); auto* transport_factory_name = config->get("transport", "http"); auto it = transport_factories.find(transport_factory_name); if (it == transport_factories.end()) { logger->err("Unknown or unsupported transport: `%s'", transport_factory_name); return false; } handler_ = Transport::create_default_handler(logger_, site_->handler()); transport_ = it->second->create(logger_, looper_, runner_, logger, config.get(), handler_.get()); if (!transport_) return false; } if (!inet::bind_and_listen(logger, config->get("bind", ""), config->get("port", "5555"), &listen_)) return false; assert(!listen_.empty()); for (auto& fd : listen_) looper_->add(fd.get(), Looper::EVENT_READ, std::bind(&Server::accept, this, fd.get(), std::placeholders::_1)); return true; } bool run() { assert(logger_); assert(looper_); assert(transport_); if (listen_.empty()) return true; auto int_handler = SignalHandler::create( looper_, SignalHandler::Signal::INT, std::bind(&Looper::quit, looper_)); auto term_handler = SignalHandler::create( looper_, SignalHandler::Signal::TERM, std::bind(&Looper::quit, looper_)); auto hup_handler = SignalHandler::create( looper_, SignalHandler::Signal::HUP, std::bind(&Server::reload, this)); travel_->start(); site_->start(); return looper_->run(logger_.get()); } private: void reload() { travel_->reload(); site_->reload(); } void accept(int listen_fd, uint8_t event) { if (event & Looper::EVENT_READ) { auto fd = inet::accept(logger_.get(), listen_fd, true); if (fd) { transport_->add_client(std::move(fd)); } return; } if (event & Looper::EVENT_ERROR) { looper_->remove(listen_fd); auto it = std::find_if(listen_.begin(), listen_.end(), [listen_fd] (auto& unique_fd) -> bool { return unique_fd.get() == listen_fd; }); if (it == listen_.end()) { assert(false); } else { listen_.erase(it); } if (listen_.empty()) looper_->quit(); return; } // Event should be either read or error assert(false); } std::shared_ptr logger_; std::shared_ptr looper_; std::shared_ptr runner_; std::shared_ptr travel_; std::unique_ptr site_; std::unique_ptr handler_; std::unique_ptr transport_; std::vector listen_; }; constexpr const char kTryMessage[] = "Try `travel3-server --help` for usage."; } // namespace int main(int argc, char** argv) { auto args = Args::create(); auto* help_arg = args->add_option('h', "help", "display this text and exit."); auto* version_arg = args->add_option('V', "version", "display version and exit."); auto* daemon_arg = args->add_option( 'D', "daemon", "fork a daemon process, logging to syslog per default"); auto* log_arg = args->add_option_with_arg( 'L', "log", "log to FILE instead of default (stdio or syslog)", "FILE"); auto* config_arg = args->add_option_with_arg( 'C', "config", "load config from CONFIG instead of default travel3.conf" " in current directory.", "CONFIG"); std::vector arguments; if (!args->run(argc, argv, "travel3-server", std::cerr, &arguments)) { std::cerr << kTryMessage << std::endl; return EXIT_FAILURE; } if (!arguments.empty()) { std::cerr << "Unexpected arguments.\n" << kTryMessage << std::endl; return EXIT_FAILURE; } if (help_arg->is_set()) { std::cout << "Usage: `travel3-servers [OPTIONS...]'\n" << "Starts the travel server to receive and handle requests.\n" << "\n" << "Options:\n"; args->print_descriptions(std::cout, 80); return EXIT_SUCCESS; } if (version_arg->is_set()) { std::cout << "Travel " VERSION " written by " "Joel Klinghed ." << std::endl; return EXIT_SUCCESS; } Server server; // Setup errors will always be logged to stdio to make them more visible. auto logger = Logger::create_stdio(); if (!server.setup(logger.get(), config_arg, log_arg, [daemon_arg] () { return daemon_arg->is_set() ? Logger::create_syslog("travel3") : Logger::create_stdio(); })) return EXIT_FAILURE; if (daemon_arg->is_set()) { auto pid = fork(); if (pid == -1) { logger->err("Failed to fork(): %s", strerror(errno)); return EXIT_FAILURE; } if (pid == 0) { // Daemon process chdir("/"); setpgrp(); close(STDIN_FILENO); close(STDOUT_FILENO); close(STDERR_FILENO); return server.run() ? EXIT_SUCCESS : EXIT_FAILURE; } else { return EXIT_SUCCESS; } } else { return server.run() ? EXIT_SUCCESS : EXIT_FAILURE; } }