limenkad.cpp raw

   1  // Copyright (c) 2009-2010 Satoshi Nakamoto
   2  // Copyright (c) 2009-2022 The Limenka developers
   3  // Distributed under the MIT software license, see the accompanying
   4  // file COPYING or http://www.opensource.org/licenses/mit-license.php.
   5  
   6  #include <limenka-build-config.h> // IWYU pragma: keep
   7  
   8  #include <chainparams.h>
   9  #include <clientversion.h>
  10  #include <common/args.h>
  11  #include <common/init.h>
  12  #include <common/system.h>
  13  #include <compat/compat.h>
  14  #include <init.h>
  15  #include <interfaces/chain.h>
  16  #include <interfaces/init.h>
  17  #include <kernel/context.h>
  18  #include <node/context.h>
  19  #include <node/interface_ui.h>
  20  #include <node/warnings.h>
  21  #include <noui.h>
  22  #include <util/check.h>
  23  #include <util/exception.h>
  24  #include <util/signalinterrupt.h>
  25  #include <util/strencodings.h>
  26  #include <util/syserror.h>
  27  #include <util/threadnames.h>
  28  #include <util/tokenpipe.h>
  29  #include <util/translation.h>
  30  
  31  #include <any>
  32  #include <functional>
  33  #include <optional>
  34  
  35  using node::NodeContext;
  36  
  37  const TranslateFn G_TRANSLATION_FUN{nullptr};
  38  
  39  #if HAVE_DECL_FORK
  40  
  41  /** Custom implementation of daemon(). This implements the same order of operations as glibc.
  42   * Opens a pipe to the child process to be able to wait for an event to occur.
  43   *
  44   * @returns 0 if successful, and in child process.
  45   *          >0 if successful, and in parent process.
  46   *          -1 in case of error (in parent process).
  47   *
  48   *          In case of success, endpoint will be one end of a pipe from the child to parent process,
  49   *          which can be used with TokenWrite (in the child) or TokenRead (in the parent).
  50   */
  51  int fork_daemon(bool nochdir, bool noclose, TokenPipeEnd& endpoint)
  52  {
  53      // communication pipe with child process
  54      std::optional<TokenPipe> umbilical = TokenPipe::Make();
  55      if (!umbilical) {
  56          return -1; // pipe or pipe2 failed.
  57      }
  58  
  59      int pid = fork();
  60      if (pid < 0) {
  61          return -1; // fork failed.
  62      }
  63      if (pid != 0) {
  64          // Parent process gets read end, closes write end.
  65          endpoint = umbilical->TakeReadEnd();
  66          umbilical->TakeWriteEnd().Close();
  67  
  68          int status = endpoint.TokenRead();
  69          if (status != 0) { // Something went wrong while setting up child process.
  70              endpoint.Close();
  71              return -1;
  72          }
  73  
  74          return pid;
  75      }
  76      // Child process gets write end, closes read end.
  77      endpoint = umbilical->TakeWriteEnd();
  78      umbilical->TakeReadEnd().Close();
  79  
  80  #if HAVE_DECL_SETSID
  81      if (setsid() < 0) {
  82          exit(1); // setsid failed.
  83      }
  84  #endif
  85  
  86      if (!nochdir) {
  87          if (chdir("/") != 0) {
  88              exit(1); // chdir failed.
  89          }
  90      }
  91      if (!noclose) {
  92          // Open /dev/null, and clone it into STDIN, STDOUT and STDERR to detach
  93          // from terminal.
  94          int fd = open("/dev/null", O_RDWR);
  95          if (fd >= 0) {
  96              bool err = dup2(fd, STDIN_FILENO) < 0 || dup2(fd, STDOUT_FILENO) < 0 || dup2(fd, STDERR_FILENO) < 0;
  97              // Don't close if fd<=2 to try to handle the case where the program was invoked without any file descriptors open.
  98              if (fd > 2) close(fd);
  99              if (err) {
 100                  exit(1); // dup2 failed.
 101              }
 102          } else {
 103              exit(1); // open /dev/null failed.
 104          }
 105      }
 106      endpoint.TokenWrite(0); // Success
 107      return 0;
 108  }
 109  
 110  #endif
 111  
 112  static bool ParseArgs(NodeContext& node, int argc, char* argv[])
 113  {
 114      ArgsManager& args{*Assert(node.args)};
 115      // If Qt is used, parameters/limenka.conf are parsed in qt/limenka.cpp's main()
 116      SetupServerArgs(args, node.init->canListenIpc());
 117      std::string error;
 118      if (!args.ParseParameters(argc, argv, error)) {
 119          return InitError(Untranslated(strprintf("Error parsing command line arguments: %s", error)));
 120      }
 121  
 122      if (auto error = common::InitConfig(args)) {
 123          return InitError(error->message, error->details);
 124      }
 125  
 126      // Error out when loose non-argument tokens are encountered on command line
 127      for (int i = 1; i < argc; i++) {
 128          if (!IsSwitchChar(argv[i][0])) {
 129              return InitError(Untranslated(strprintf("Command line contains unexpected token '%s', see limenkad -h for a list of options.", argv[i])));
 130          }
 131      }
 132      return true;
 133  }
 134  
 135  static bool ProcessInitCommands(ArgsManager& args)
 136  {
 137      // Process help and version before taking care about datadir
 138      if (HelpRequested(args) || args.GetBoolArg("-version", false)) {
 139          std::string strUsage = CLIENT_NAME " daemon version " + FormatFullVersion() + "\n";
 140  
 141          if (args.GetBoolArg("-version", false)) {
 142              strUsage += FormatParagraph(LicenseInfo());
 143          } else {
 144              strUsage += "\n"
 145                  "The " CLIENT_NAME " daemon (limenkad) is a headless program that connects to the Limenka network to validate and relay transactions and blocks, as well as relaying addresses.\n\n"
 146                  "It provides the backbone of the Limenka network and its RPC, REST and ZMQ services can provide various transaction, block and address-related services.\n\n"
 147                  "There is an optional wallet component which provides transaction services.\n\n"
 148                  "It can be used in a headless environment or as part of a server setup.\n"
 149                  "\n"
 150                  "Usage: limenkad [options]\n"
 151                  "\n";
 152              strUsage += args.GetHelpMessage();
 153          }
 154  
 155          tfm::format(std::cout, "%s", strUsage);
 156          return true;
 157      }
 158  
 159      return false;
 160  }
 161  
 162  static bool AppInit(NodeContext& node)
 163  {
 164      bool fRet = false;
 165      ArgsManager& args = *Assert(node.args);
 166  
 167  #if HAVE_DECL_FORK
 168      // Communication with parent after daemonizing. This is used for signalling in the following ways:
 169      // - a boolean token is sent when the initialization process (all the Init* functions) have finished to indicate
 170      // that the parent process can quit, and whether it was successful/unsuccessful.
 171      // - an unexpected shutdown of the child process creates an unexpected end of stream at the parent
 172      // end, which is interpreted as failure to start.
 173      TokenPipeEnd daemon_ep;
 174  #endif
 175      std::any context{&node};
 176      try
 177      {
 178          // -server defaults to true for limenkad but not for the GUI so do this here
 179          args.SoftSetBoolArg("-server", true);
 180          // Set this early so that parameter interactions go to console
 181          InitLogging(args);
 182          InitParameterInteraction(args);
 183          if (!AppInitBasicSetup(args, node.exit_status)) {
 184              // InitError will have been called with detailed error, which ends up on console
 185              return false;
 186          }
 187          if (!AppInitParameterInteraction(args)) {
 188              // InitError will have been called with detailed error, which ends up on console
 189              return false;
 190          }
 191  
 192          node.warnings = std::make_unique<node::Warnings>();
 193  
 194          node.kernel = std::make_unique<kernel::Context>();
 195          node.ecc_context = std::make_unique<ECC_Context>();
 196          if (!AppInitSanityChecks(*node.kernel))
 197          {
 198              // InitError will have been called with detailed error, which ends up on console
 199              return false;
 200          }
 201  
 202          if (args.GetBoolArg("-daemon", DEFAULT_DAEMON) || args.GetBoolArg("-daemonwait", DEFAULT_DAEMONWAIT)) {
 203  #if HAVE_DECL_FORK
 204              tfm::format(std::cout, CLIENT_NAME " starting\n");
 205  
 206              // Daemonize
 207              switch (fork_daemon(1, 0, daemon_ep)) { // don't chdir (1), do close FDs (0)
 208              case 0: // Child: continue.
 209                  // If -daemonwait is not enabled, immediately send a success token the parent.
 210                  if (!args.GetBoolArg("-daemonwait", DEFAULT_DAEMONWAIT)) {
 211                      daemon_ep.TokenWrite(1);
 212                      daemon_ep.Close();
 213                  }
 214                  break;
 215              case -1: // Error happened.
 216                  return InitError(Untranslated(strprintf("fork_daemon() failed: %s", SysErrorString(errno))));
 217              default: { // Parent: wait and exit.
 218                  int token = daemon_ep.TokenRead();
 219                  if (token) { // Success
 220                      exit(EXIT_SUCCESS);
 221                  } else { // fRet = false or token read error (premature exit).
 222                      tfm::format(std::cerr, "Error during initialization - check %s for details\n", fs::PathToString(LogInstance().m_file_path.filename()));
 223                      exit(EXIT_FAILURE);
 224                  }
 225              }
 226              }
 227  #else
 228              return InitError(Untranslated("-daemon is not supported on this operating system"));
 229  #endif // HAVE_DECL_FORK
 230          }
 231          // Lock critical directories after daemonization
 232          if (!AppInitLockDirectories())
 233          {
 234              // If locking a directory failed, exit immediately
 235              return false;
 236          }
 237          fRet = AppInitInterfaces(node) && AppInitMain(node);
 238      }
 239      catch (const std::exception& e) {
 240          PrintExceptionContinue(&e, "AppInit()");
 241      } catch (...) {
 242          PrintExceptionContinue(nullptr, "AppInit()");
 243      }
 244  
 245  #if HAVE_DECL_FORK
 246      if (daemon_ep.IsOpen()) {
 247          // Signal initialization status to parent, then close pipe.
 248          daemon_ep.TokenWrite(fRet);
 249          daemon_ep.Close();
 250      }
 251  #endif
 252      return fRet;
 253  }
 254  
 255  MAIN_FUNCTION
 256  {
 257  #ifdef WIN32
 258      common::WinCmdLineArgs winArgs;
 259      std::tie(argc, argv) = winArgs.get();
 260  #endif
 261  
 262      NodeContext node;
 263      int exit_status;
 264      std::unique_ptr<interfaces::Init> init = interfaces::MakeNodeInit(node, argc, argv, exit_status);
 265      if (!init) {
 266          return exit_status;
 267      }
 268  
 269      SetupEnvironment();
 270  
 271      // Connect limenkad signal handlers
 272      noui_connect();
 273  
 274      util::ThreadSetInternalName("init");
 275  
 276      // Interpret command line arguments
 277      ArgsManager& args = *Assert(node.args);
 278      if (!ParseArgs(node, argc, argv)) return EXIT_FAILURE;
 279      // Process early info return commands such as -help or -version
 280      if (ProcessInitCommands(args)) return EXIT_SUCCESS;
 281  
 282      // Start application
 283      if (!AppInit(node) || !Assert(node.shutdown_signal)->wait()) {
 284          node.exit_status = EXIT_FAILURE;
 285      }
 286      Interrupt(node);
 287      Shutdown(node);
 288  
 289      return node.exit_status;
 290  }
 291