main.cpp 6.58 KB
Newer Older
1
#include <xmpp/biboumi_component.hpp>
2
#include <utils/timed_events.hpp>
3 4
#include <network/poller.hpp>
#include <config/config.hpp>
louiz’'s avatar
louiz’ committed
5
#include <logger/logger.hpp>
louiz’'s avatar
louiz’ committed
6
#include <utils/reload.hpp>
7

8
#include <iostream>
9
#include <memory>
louiz’'s avatar
louiz’ committed
10
#include <atomic>
11
#include <cstdlib>
louiz’'s avatar
louiz’ committed
12 13 14

#include <signal.h>

15 16 17 18
#ifdef CARES_FOUND
# include <network/dns_handler.hpp>
#endif

louiz’'s avatar
louiz’ committed
19
// A flag set by the SIGINT signal handler.
louiz’'s avatar
louiz’ committed
20 21 22
static volatile std::atomic<bool> stop(false);
// Flag set by the SIGUSR1/2 signal handler.
static volatile std::atomic<bool> reload(false);
louiz’'s avatar
louiz’ committed
23 24 25
// 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;
26

27 28 29 30 31 32 33 34 35 36 37 38 39 40 41
/**
 * 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())
    std::cerr << "Error: empty value for option " << missing_option << "." << std::endl;
  std::cerr <<
    "Please provide a configuration file filled like this:\n\n"
    "hostname=irc.example.com\npassword=S3CR3T"
            << std::endl;
  return 1;
}

42
static void sigint_handler(int sig, siginfo_t*, void*)
louiz’'s avatar
louiz’ committed
43
{
44 45 46 47 48 49 50 51 52 53 54 55
  // We reset the SIGTERM or SIGINT (the one that didn't trigger this
  // handler) signal handler to its default value.  This avoid calling this
  // handler twice, if the process receive both signals in a quick
  // succession.
  int sig_to_reset = (sig == SIGINT? SIGTERM: SIGINT);
  sigset_t mask;
  sigemptyset(&mask);
  struct sigaction sigreset = {};
  sigreset.sa_handler = SIG_DFL;
  sigreset.sa_mask = mask;
  sigaction(sig_to_reset, &sigreset, nullptr);

56
  // In 2 seconds, repeat the same signal, to force the exit
57
  TimedEventsManager::instance().add_event(TimedEvent(std::chrono::steady_clock::now() + 2s,
58
                                    [sig]() { raise(sig); }));
59
  stop.store(true);
louiz’'s avatar
louiz’ committed
60 61
}

louiz’'s avatar
louiz’ committed
62 63
static void sigusr_handler(int, siginfo_t*, void*)
{
64
  reload.store(true);
louiz’'s avatar
louiz’ committed
65 66
}

67
int main(int ac, char** av)
louiz’'s avatar
louiz’ committed
68
{
69 70
  if (ac > 1)
    Config::filename = av[1];
71
  else
72 73
  {
    const char* xdg_config_home = getenv("XDG_CONFIG_HOME");
74
    if (xdg_config_home && xdg_config_home[0] == '/')
75 76 77 78 79 80 81 82 83 84
      Config::filename = std::string{xdg_config_home} + "/" "biboumi" "/" "biboumi.cfg";
    else
    {
      const char* home = getenv("HOME");
      if (home)
        Config::filename = std::string{home} + "/" ".config" "/" "biboumi" "/" "biboumi.cfg";
      else
        Config::filename = "biboumi.cfg";
    }
  }
85

86
  Config::file_must_exist = true;
87
  std::cerr << "Using configuration file: " << Config::filename << std::endl;
88 89 90 91 92 93

  std::string password;
  try { // The file must exist
    password = Config::get("password", "");
  }
  catch (const std::ios::failure& e) {
94
    return config_help("");
95
  }
96
  const std::string hostname = Config::get("hostname", "");
97
  if (password.empty())
98
    return config_help("password");
99 100
  if (hostname.empty())
    return config_help("hostname");
101

102 103 104 105 106 107 108 109 110 111 112
  // 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);

louiz’'s avatar
louiz’ committed
113 114 115
  // Install the signals used to exit the process cleanly, or reload the
  // config
  sigemptyset(&mask);
louiz’'s avatar
louiz’ committed
116 117 118
  struct sigaction on_sigint;
  on_sigint.sa_sigaction = &sigint_handler;
  on_sigint.sa_mask = mask;
louiz’'s avatar
louiz’ committed
119 120
  // we want to catch that signal only once.
  // Sending SIGINT again will "force" an exit
louiz’'s avatar
louiz’ committed
121 122 123 124 125 126 127 128
  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;
  on_sigusr.sa_mask = mask;
louiz’'s avatar
louiz’ committed
129
  on_sigusr.sa_flags = 0;
louiz’'s avatar
louiz’ committed
130 131
  sigaction(SIGUSR1, &on_sigusr, nullptr);
  sigaction(SIGUSR2, &on_sigusr, nullptr);
louiz’'s avatar
louiz’ committed
132

louiz’'s avatar
louiz’ committed
133 134 135
  auto p = std::make_shared<Poller>();
  auto xmpp_component =
    std::make_shared<BiboumiComponent>(p, hostname, password);
136 137
  xmpp_component->start();

138 139 140
#ifdef CARES_FOUND
  DNSHandler::instance.watch_dns_sockets(p);
#endif
141
  auto timeout = TimedEventsManager::instance().get_timeout();
142
  while (p->poll(timeout) != -1)
louiz’'s avatar
louiz’ committed
143
  {
144
    TimedEventsManager::instance().execute_expired_events();
louiz’'s avatar
louiz’ committed
145 146 147
    // Check for empty irc_clients (not connected, or with no joined
    // channel) and remove them
    xmpp_component->clean();
louiz’'s avatar
louiz’ committed
148 149 150 151
    if (stop)
    {
      log_info("Signal received, exiting...");
      exiting = true;
152
      stop.store(false);
louiz’'s avatar
louiz’ committed
153
      xmpp_component->shutdown();
louiz’'s avatar
louiz’ committed
154
      // Cancel the timer for a potential reconnection
155
      TimedEventsManager::instance().cancel("XMPP reconnection");
louiz’'s avatar
louiz’ committed
156
    }
louiz’'s avatar
louiz’ committed
157 158 159
    if (reload)
    {
      log_info("Signal received, reloading the config...");
louiz’'s avatar
louiz’ committed
160
      ::reload_process();
161
      reload.store(false);
louiz’'s avatar
louiz’ committed
162
    }
163 164 165 166
    // 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
167 168
    if (!exiting && xmpp_component->ever_auth &&
        !xmpp_component->is_connected() &&
169
        !xmpp_component->is_connecting())
170 171 172
    {
      if (xmpp_component->first_connection_try == true)
      { // immediately re-try to connect
173 174 175
        xmpp_component->reset();
        xmpp_component->start();
      }
176 177 178 179 180 181 182 183 184 185 186 187
      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));
      }
    }
louiz’'s avatar
louiz’ committed
188 189
    // If the only existing connection is the one to the XMPP component:
    // close the XMPP stream.
190 191
    if (exiting && xmpp_component->is_connecting())
      xmpp_component->close();
192
    if (exiting && p->size() == 1 && xmpp_component->is_document_open())
louiz’'s avatar
louiz’ committed
193
      xmpp_component->close_document();
194 195 196 197
#ifdef CARES_FOUND
    if (!exiting)
      DNSHandler::instance.watch_dns_sockets(p);
#endif
198 199 200 201
    if (exiting) // If we are exiting, do not wait for any timed event
      timeout = utils::no_timeout;
    else
      timeout = TimedEventsManager::instance().get_timeout();
louiz’'s avatar
louiz’ committed
202
  }
louiz’'s avatar
louiz’ committed
203 204 205
#ifdef CARES_FOUND
  DNSHandler::instance.destroy();
#endif
louiz’'s avatar
louiz’ committed
206
  log_info("All connections cleanly closed, have a nice day.");
louiz’'s avatar
louiz’ committed
207 208
  return 0;
}