diff options
Diffstat (limited to 'src/main.cpp')
-rw-r--r-- | src/main.cpp | 202 |
1 files changed, 202 insertions, 0 deletions
diff --git a/src/main.cpp b/src/main.cpp new file mode 100644 index 0000000..53f3193 --- /dev/null +++ b/src/main.cpp @@ -0,0 +1,202 @@ +#include <xmpp/biboumi_component.hpp> +#include <utils/timed_events.hpp> +#include <network/poller.hpp> +#include <config/config.hpp> +#include <logger/logger.hpp> +#include <utils/xdg.hpp> +#include <utils/reload.hpp> + +#ifdef CARES_FOUND +# include <network/dns_handler.hpp> +#endif + +#include <atomic> +#include <signal.h> + +// A flag set by the SIGINT signal handler. +static volatile std::atomic<bool> stop(false); +// Flag set by the SIGUSR1/2 signal handler. +static volatile std::atomic<bool> reload(false); +// A flag indicating that we are wanting to exit the process. i.e: if this +// flag is set and all connections are closed, we can exit properly. +static bool exiting = false; + +/** + * Provide an helpful message to help the user write a minimal working + * configuration file. + */ +int config_help(const std::string& missing_option) +{ + if (!missing_option.empty()) + log_error("Configuration error: empty value for option ", missing_option, "."); + log_error("Please provide a configuration file filled like this:\n\n" + "hostname=irc.example.com\npassword=S3CR3T"); + return 1; +} + +int display_help() +{ + std::cout << "Usage: biboumi [configuration_file]" << std::endl; + return 0; +} + +static void sigint_handler(int sig, siginfo_t*, void*) +{ + // In 2 seconds, repeat the same signal, to force the exit + TimedEventsManager::instance().add_event(TimedEvent(std::chrono::steady_clock::now() + 2s, + [sig]() { raise(sig); })); + stop.store(true); +} + +static void sigusr_handler(int, siginfo_t*, void*) +{ + reload.store(true); +} + +int main(int ac, char** av) +{ + if (ac > 1) + { + const std::string arg = av[1]; + if (arg.size() >= 2 && arg[0] == '-' && arg[1] == '-') + { + if (arg == "--help") + return display_help(); + else + { + std::cerr << "Unknow command line option: " << arg << std::endl; + return 1; + } + } + } + const std::string conf_filename = ac > 1 ? av[1] : xdg_config_path("biboumi.cfg"); + std::cout << "Using configuration file: " << conf_filename << std::endl; + + if (!Config::read_conf(conf_filename)) + return config_help(""); + + const std::string password = Config::get("password", ""); + if (password.empty()) + return config_help("password"); + const std::string hostname = Config::get("hostname", ""); + if (hostname.empty()) + return config_help("hostname"); + + try { + open_database(); + } catch (...) { + return 1; + } + + // Block the signals we want to manage. They will be unblocked only during + // the epoll_pwait or ppoll calls. This avoids some race conditions, + // explained in man 2 pselect on linux + sigset_t mask; + sigemptyset(&mask); + sigaddset(&mask, SIGINT); + sigaddset(&mask, SIGTERM); + sigaddset(&mask, SIGUSR1); + sigaddset(&mask, SIGUSR2); + sigprocmask(SIG_BLOCK, &mask, nullptr); + + // Install the signals used to exit the process cleanly, or reload the + // config + struct sigaction on_sigint; + on_sigint.sa_sigaction = &sigint_handler; + // All signals must be blocked while a signal handler is running + sigfillset(&on_sigint.sa_mask); + // we want to catch that signal only once. + // Sending SIGINT again will "force" an exit + on_sigint.sa_flags = SA_RESETHAND; + sigaction(SIGINT, &on_sigint, nullptr); + sigaction(SIGTERM, &on_sigint, nullptr); + + // Install a signal to reload the config on SIGUSR1/2 + struct sigaction on_sigusr; + on_sigusr.sa_sigaction = &sigusr_handler; + sigfillset(&on_sigusr.sa_mask); + on_sigusr.sa_flags = 0; + sigaction(SIGUSR1, &on_sigusr, nullptr); + sigaction(SIGUSR2, &on_sigusr, nullptr); + + auto p = std::make_shared<Poller>(); + auto xmpp_component = + std::make_shared<BiboumiComponent>(p, hostname, password); + xmpp_component->start(); + +#ifdef CARES_FOUND + DNSHandler::instance.watch_dns_sockets(p); +#endif + auto timeout = TimedEventsManager::instance().get_timeout(); + while (p->poll(timeout) != -1) + { + TimedEventsManager::instance().execute_expired_events(); + // Check for empty irc_clients (not connected, or with no joined + // channel) and remove them + xmpp_component->clean(); + if (stop) + { + log_info("Signal received, exiting..."); +#ifdef SYSTEMD_FOUND + sd_notify(0, "STOPPING=1"); +#endif + exiting = true; + stop.store(false); + xmpp_component->shutdown(); + // Cancel the timer for a potential reconnection + TimedEventsManager::instance().cancel("XMPP reconnection"); + } + if (reload) + { + log_info("Signal received, reloading the config..."); + ::reload_process(); + reload.store(false); + } + // Reconnect to the XMPP server if this was not intended. This may have + // happened because we sent something invalid to it and it decided to + // close the connection. This is a bug that should be fixed, but we + // still reconnect automatically instead of dropping everything + if (!exiting && xmpp_component->ever_auth && + !xmpp_component->is_connected() && + !xmpp_component->is_connecting()) + { + if (xmpp_component->first_connection_try == true) + { // immediately re-try to connect + xmpp_component->reset(); + xmpp_component->start(); + } + else + { // Re-connecting failed, we now try only each few seconds + auto reconnect_later = [xmpp_component]() + { + xmpp_component->reset(); + xmpp_component->start(); + }; + TimedEvent event(std::chrono::steady_clock::now() + 2s, + reconnect_later, "XMPP reconnection"); + TimedEventsManager::instance().add_event(std::move(event)); + } + } + // If the only existing connection is the one to the XMPP component: + // close the XMPP stream. + if (exiting && xmpp_component->is_connecting()) + xmpp_component->close(); + if (exiting && p->size() == 1 && xmpp_component->is_document_open()) + xmpp_component->close_document(); +#ifdef CARES_FOUND + if (!exiting) + DNSHandler::instance.watch_dns_sockets(p); +#endif + if (exiting) // If we are exiting, do not wait for any timed event + timeout = utils::no_timeout; + else + timeout = TimedEventsManager::instance().get_timeout(); + } +#ifdef CARES_FOUND + DNSHandler::instance.destroy(); +#endif + if (!xmpp_component->ever_auth) + return 1; // To signal that the process did not properly start + log_info("All connections cleanly closed, have a nice day."); + return 0; +} |