summaryrefslogtreecommitdiff
path: root/src/main.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/main.cpp')
-rw-r--r--src/main.cpp202
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;
+}