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

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

#include <signal.h>

// A flag set by the SIGINT signal handler.
louiz’'s avatar
louiz’ committed
15 16 17
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
18 19 20
// 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;
21

22 23 24 25 26 27 28 29 30 31 32 33 34 35 36
/**
 * 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;
}

37
static void sigint_handler(int sig, siginfo_t*, void*)
louiz’'s avatar
louiz’ committed
38
{
39
  // In 2 seconds, repeat the same signal, to force the exit
40
  TimedEventsManager::instance().add_event(TimedEvent(std::chrono::steady_clock::now() + 2s,
41
                                    [sig]() { raise(sig); }));
42
  stop.store(true);
louiz’'s avatar
louiz’ committed
43 44
}

louiz’'s avatar
louiz’ committed
45 46
static void sigusr_handler(int, siginfo_t*, void*)
{
47
  reload.store(true);
louiz’'s avatar
louiz’ committed
48 49
}

50
int main(int ac, char** av)
louiz’'s avatar
louiz’ committed
51
{
52 53 54
  if (ac > 1)
    Config::filename = av[1];
  Config::file_must_exist = true;
55
  std::cerr << "Using configuration file: " << Config::filename << std::endl;
56 57 58 59 60 61

  std::string password;
  try { // The file must exist
    password = Config::get("password", "");
  }
  catch (const std::ios::failure& e) {
62
    return config_help("");
63
  }
64
  const std::string hostname = Config::get("hostname", "");
65
  if (password.empty())
66
    return config_help("password");
67 68
  if (hostname.empty())
    return config_help("hostname");
69

70 71 72 73
  auto p = std::make_shared<Poller>();
  auto xmpp_component = std::make_shared<XmppComponent>(p,
                                                        hostname,
                                                        password);
louiz’'s avatar
louiz’ committed
74 75 76 77 78

  // Install the signals used to exit the process cleanly, or reload the
  // config
  sigset_t mask;
  sigemptyset(&mask);
louiz’'s avatar
louiz’ committed
79 80 81
  struct sigaction on_sigint;
  on_sigint.sa_sigaction = &sigint_handler;
  on_sigint.sa_mask = mask;
louiz’'s avatar
louiz’ committed
82 83
  // we want to catch that signal only once.
  // Sending SIGINT again will "force" an exit
louiz’'s avatar
louiz’ committed
84 85 86 87 88 89 90 91
  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
92
  on_sigusr.sa_flags = 0;
louiz’'s avatar
louiz’ committed
93 94
  sigaction(SIGUSR1, &on_sigusr, nullptr);
  sigaction(SIGUSR2, &on_sigusr, nullptr);
louiz’'s avatar
louiz’ committed
95

96 97
  xmpp_component->start();

98
  auto timeout = TimedEventsManager::instance().get_timeout();
99
  while (p->poll(timeout) != -1)
louiz’'s avatar
louiz’ committed
100
  {
101
    TimedEventsManager::instance().execute_expired_events();
louiz’'s avatar
louiz’ committed
102 103 104
    // Check for empty irc_clients (not connected, or with no joined
    // channel) and remove them
    xmpp_component->clean();
louiz’'s avatar
louiz’ committed
105 106 107 108
    if (stop)
    {
      log_info("Signal received, exiting...");
      exiting = true;
109
      stop.store(false);
louiz’'s avatar
louiz’ committed
110 111
      xmpp_component->shutdown();
    }
louiz’'s avatar
louiz’ committed
112 113 114 115 116 117 118 119 120
    if (reload)
    {
      // Closing the config will just force it to be reopened the next time
      // a configuration option is needed
      log_info("Signal received, reloading the config...");
      Config::close();
      // Destroy the logger instance, to be recreated the next time a log
      // line needs to be written
      Logger::instance().reset();
121
      reload.store(false);
louiz’'s avatar
louiz’ committed
122
    }
123 124 125 126
    // 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
127 128
    if (!exiting && xmpp_component->ever_auth &&
        !xmpp_component->is_connected() &&
129 130 131 132 133
        !xmpp_component->is_connecting())
      {
        xmpp_component->reset();
        xmpp_component->start();
      }
louiz’'s avatar
louiz’ committed
134 135
    // If the only existing connection is the one to the XMPP component:
    // close the XMPP stream.
136 137
    if (exiting && xmpp_component->is_connecting())
      xmpp_component->close();
138
    if (exiting && p->size() == 1 && xmpp_component->is_document_open())
louiz’'s avatar
louiz’ committed
139
      xmpp_component->close_document();
140
    timeout = TimedEventsManager::instance().get_timeout();
louiz’'s avatar
louiz’ committed
141 142
  }
  log_info("All connection cleanely closed, have a nice day.");
louiz’'s avatar
louiz’ committed
143 144
  return 0;
}