args.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 <common/args.h>
   7  
   8  #include <chainparamsbase.h>
   9  #include <common/settings.h>
  10  #include <logging.h>
  11  #include <sync.h>
  12  #include <tinyformat.h>
  13  #include <univalue.h>
  14  #include <util/chaintype.h>
  15  #include <util/check.h>
  16  #include <util/fs.h>
  17  #include <util/fs_helpers.h>
  18  #include <util/strencodings.h>
  19  #include <util/string.h>
  20  
  21  #ifdef WIN32
  22  #include <codecvt>    /* for codecvt_utf8_utf16 */
  23  #include <shellapi.h> /* for CommandLineToArgvW */
  24  #include <shlobj.h>   /* for CSIDL_APPDATA */
  25  #endif
  26  
  27  #include <algorithm>
  28  #include <cassert>
  29  #include <cstdint>
  30  #include <cstdlib>
  31  #include <cstring>
  32  #include <fstream>
  33  #include <map>
  34  #include <optional>
  35  #include <stdexcept>
  36  #include <string>
  37  #include <unordered_set>
  38  #include <utility>
  39  #include <variant>
  40  
  41  const char * const LIMENKA_CONF_FILENAME = "limenka.conf";
  42  const char * const LIMENKA_SETTINGS_FILENAME = "settings.json";
  43  const char * const LIMENKA_RW_CONF_FILENAME = "limenka_rw.conf";
  44  
  45  ArgsManager gArgs;
  46  
  47  /**
  48   * Interpret a string argument as a boolean.
  49   *
  50   * The definition of LocaleIndependentAtoi<int>() requires that non-numeric string values
  51   * like "foo", return 0. This means that if a user unintentionally supplies a
  52   * non-integer argument here, the return value is always false. This means that
  53   * -foo=false does what the user probably expects, but -foo=true is well defined
  54   * but does not do what they probably expected.
  55   *
  56   * The return value of LocaleIndependentAtoi<int>(...) is zero when given input not
  57   * representable as an int.
  58   *
  59   * For a more extensive discussion of this topic (and a wide range of opinions
  60   * on the Right Way to change this code), see PR12713.
  61   */
  62  static bool InterpretBool(const std::string& strValue)
  63  {
  64      if (strValue.empty())
  65          return true;
  66      return (LocaleIndependentAtoi<int>(strValue) != 0);
  67  }
  68  
  69  static std::string SettingName(const std::string& arg)
  70  {
  71      return arg.size() > 0 && arg[0] == '-' ? arg.substr(1) : arg;
  72  }
  73  
  74  /**
  75   * Parse "name", "section.name", "noname", "section.noname" settings keys.
  76   *
  77   * @note Where an option was negated can be later checked using the
  78   * IsArgNegated() method. One use case for this is to have a way to disable
  79   * options that are not normally boolean (e.g. using -nodebuglogfile to request
  80   * that debug log output is not sent to any file at all).
  81   */
  82  KeyInfo InterpretKey(std::string key)
  83  {
  84      KeyInfo result;
  85      // Split section name from key name for keys like "testnet.foo" or "regtest.bar"
  86      size_t option_index = key.find('.');
  87      if (option_index != std::string::npos) {
  88          result.section = key.substr(0, option_index);
  89          key.erase(0, option_index + 1);
  90      }
  91      if (key.substr(0, 2) == "no") {
  92          key.erase(0, 2);
  93          result.negated = true;
  94      }
  95      result.name = key;
  96      return result;
  97  }
  98  
  99  /**
 100   * Interpret settings value based on registered flags.
 101   *
 102   * @param[in]   key      key information to know if key was negated
 103   * @param[in]   value    string value of setting to be parsed
 104   * @param[in]   flags    ArgsManager registered argument flags
 105   * @param[out]  error    Error description if settings value is not valid
 106   *
 107   * @return parsed settings value if it is valid, otherwise nullopt accompanied
 108   * by a descriptive error string
 109   */
 110  std::optional<common::SettingsValue> InterpretValue(const KeyInfo& key, const std::string* value,
 111                                                    unsigned int flags, std::string& error)
 112  {
 113      // Return negated settings as false values.
 114      if (key.negated) {
 115          if (flags & ArgsManager::DISALLOW_NEGATION) {
 116              error = strprintf("Negating of -%s is meaningless and therefore forbidden", key.name);
 117              return std::nullopt;
 118          }
 119          // Double negatives like -nofoo=0 are supported (but discouraged)
 120          if (value && !InterpretBool(*value)) {
 121              LogWarning("Parsed potentially confusing double-negative -%s=%s", key.name, *value);
 122              return true;
 123          }
 124          return false;
 125      }
 126      if (!value && (flags & ArgsManager::DISALLOW_ELISION)) {
 127          error = strprintf("Can not set -%s with no value. Please specify value with -%s=value.", key.name, key.name);
 128          return std::nullopt;
 129      }
 130      return value ? *value : "";
 131  }
 132  
 133  // Define default constructor and destructor that are not inline, so code instantiating this class doesn't need to
 134  // #include class definitions for all members.
 135  // For example, m_settings has an internal dependency on univalue.
 136  ArgsManager::ArgsManager() = default;
 137  ArgsManager::~ArgsManager() = default;
 138  
 139  std::set<std::string> ArgsManager::GetUnsuitableSectionOnlyArgs() const
 140  {
 141      std::set<std::string> unsuitables;
 142  
 143      LOCK(cs_args);
 144  
 145      // if there's no section selected, don't worry
 146      if (m_network.empty()) return std::set<std::string> {};
 147  
 148      // if it's okay to use the default section for this network, don't worry
 149      if (m_network == ChainTypeToString(ChainType::MAIN)) return std::set<std::string> {};
 150  
 151      for (const auto& arg : m_network_only_args) {
 152          if (OnlyHasDefaultSectionSetting(m_settings, m_network, SettingName(arg))) {
 153              unsuitables.insert(arg);
 154          }
 155      }
 156      return unsuitables;
 157  }
 158  
 159  std::list<SectionInfo> ArgsManager::GetUnrecognizedSections() const
 160  {
 161      // Section names to be recognized in the config file.
 162      static const std::set<std::string> available_sections{
 163          ChainTypeToString(ChainType::REGTEST),
 164          ChainTypeToString(ChainType::SIGNET),
 165          ChainTypeToString(ChainType::TESTNET),
 166          ChainTypeToString(ChainType::TESTNET4),
 167          ChainTypeToString(ChainType::MAIN),
 168      };
 169  
 170      LOCK(cs_args);
 171      std::list<SectionInfo> unrecognized = m_config_sections;
 172      unrecognized.remove_if([](const SectionInfo& appeared){ return available_sections.find(appeared.m_name) != available_sections.end(); });
 173      return unrecognized;
 174  }
 175  
 176  void ArgsManager::SelectConfigNetwork(const std::string& network)
 177  {
 178      LOCK(cs_args);
 179      m_network = network;
 180  }
 181  
 182  bool ArgsManager::ParseParameters(int argc, const char* const argv[], std::string& error)
 183  {
 184      LOCK(cs_args);
 185      m_settings.command_line_options.clear();
 186  
 187      for (int i = 1; i < argc; i++) {
 188          std::string key(argv[i]);
 189  
 190  #ifdef __APPLE__
 191          // At the first time when a user gets the "App downloaded from the
 192          // internet" warning, and clicks the Open button, macOS passes
 193          // a unique process serial number (PSN) as -psn_... command-line
 194          // argument, which we filter out.
 195          if (key.substr(0, 5) == "-psn_") continue;
 196  #endif
 197  
 198          if (key == "-") break; //limenka-tx using stdin
 199          std::optional<std::string> val;
 200          size_t is_index = key.find('=');
 201          if (is_index != std::string::npos) {
 202              val = key.substr(is_index + 1);
 203              key.erase(is_index);
 204          }
 205  #ifdef WIN32
 206          key = ToLower(key);
 207          if (key[0] == '/')
 208              key[0] = '-';
 209  #endif
 210  
 211          if (key[0] != '-') {
 212              if (!m_accept_any_command && m_command.empty()) {
 213                  // The first non-dash arg is a registered command
 214                  std::optional<unsigned int> flags = GetArgFlags(key);
 215                  if (!flags || !(*flags & ArgsManager::COMMAND)) {
 216                      error = strprintf("Invalid command '%s'", argv[i]);
 217                      return false;
 218                  }
 219              }
 220              m_command.push_back(key);
 221              while (++i < argc) {
 222                  // The remaining args are command args
 223                  m_command.emplace_back(argv[i]);
 224              }
 225              break;
 226          }
 227  
 228          // Transform --foo to -foo
 229          if (key.length() > 1 && key[1] == '-')
 230              key.erase(0, 1);
 231  
 232          // Transform -foo to foo
 233          key.erase(0, 1);
 234          KeyInfo keyinfo = InterpretKey(key);
 235          std::optional<unsigned int> flags = GetArgFlags('-' + keyinfo.name);
 236  
 237          // Unknown command line options and command line options with dot
 238          // characters (which are returned from InterpretKey with nonempty
 239          // section strings) are not valid.
 240          if (!flags || !keyinfo.section.empty()) {
 241              error = strprintf("Invalid parameter %s", argv[i]);
 242              return false;
 243          }
 244  
 245          std::optional<common::SettingsValue> value = InterpretValue(keyinfo, val ? &*val : nullptr, *flags, error);
 246          if (!value) return false;
 247  
 248          m_settings.command_line_options[keyinfo.name].push_back(*value);
 249      }
 250  
 251      // we do not allow -includeconf from command line, only -noincludeconf
 252      if (auto* includes = common::FindKey(m_settings.command_line_options, "includeconf")) {
 253          const common::SettingsSpan values{*includes};
 254          // Range may be empty if -noincludeconf was passed
 255          if (!values.empty()) {
 256              error = "-includeconf cannot be used from commandline; -includeconf=" + values.begin()->write();
 257              return false; // pick first value as example
 258          }
 259      }
 260      return true;
 261  }
 262  
 263  std::optional<unsigned int> ArgsManager::GetArgFlags(const std::string& name) const
 264  {
 265      LOCK(cs_args);
 266      for (const auto& arg_map : m_available_args) {
 267          const auto search = arg_map.second.find(name);
 268          if (search != arg_map.second.end()) {
 269              return search->second.m_flags;
 270          }
 271      }
 272      return std::nullopt;
 273  }
 274  
 275  fs::path ArgsManager::GetPathArg(std::string arg, const fs::path& default_value) const
 276  {
 277      if (IsArgNegated(arg)) return fs::path{};
 278      std::string path_str = GetArg(arg, "");
 279      if (path_str.empty()) return default_value;
 280      fs::path result = fs::PathFromString(path_str).lexically_normal();
 281      // Remove trailing slash, if present.
 282      return result.has_filename() ? result : result.parent_path();
 283  }
 284  
 285  fs::path ArgsManager::GetBlocksDirPath() const
 286  {
 287      LOCK(cs_args);
 288      fs::path& path = m_cached_blocks_path;
 289  
 290      // Cache the path to avoid calling fs::create_directories on every call of
 291      // this function
 292      if (!path.empty()) return path;
 293  
 294      if (IsArgSet("-blocksdir")) {
 295          path = fs::absolute(GetPathArg("-blocksdir"));
 296          if (!fs::is_directory(path)) {
 297              path = "";
 298              return path;
 299          }
 300      } else {
 301          path = GetDataDirBase();
 302      }
 303  
 304      path /= fs::PathFromString(BaseParams().DataDir());
 305      path /= "blocks";
 306      fs::create_directories(path);
 307      return path;
 308  }
 309  
 310  fs::path ArgsManager::GetDataDir(bool net_specific) const
 311  {
 312      LOCK(cs_args);
 313      fs::path& path = net_specific ? m_cached_network_datadir_path : m_cached_datadir_path;
 314  
 315      // Used cached path if available
 316      if (!path.empty()) return path;
 317  
 318      const fs::path datadir{GetPathArg("-datadir")};
 319      if (!datadir.empty()) {
 320          path = fs::absolute(datadir);
 321          if (!fs::is_directory(path)) {
 322              path = "";
 323              return path;
 324          }
 325      } else {
 326          path = GetDefaultDataDir();
 327      }
 328  
 329      if (net_specific && !BaseParams().DataDir().empty()) {
 330          path /= fs::PathFromString(BaseParams().DataDir());
 331      }
 332  
 333      return path;
 334  }
 335  
 336  void ArgsManager::ClearPathCache()
 337  {
 338      LOCK(cs_args);
 339  
 340      m_cached_datadir_path = fs::path();
 341      m_cached_network_datadir_path = fs::path();
 342      m_cached_blocks_path = fs::path();
 343  }
 344  
 345  std::optional<const ArgsManager::Command> ArgsManager::GetCommand() const
 346  {
 347      Command ret;
 348      LOCK(cs_args);
 349      auto it = m_command.begin();
 350      if (it == m_command.end()) {
 351          // No command was passed
 352          return std::nullopt;
 353      }
 354      if (!m_accept_any_command) {
 355          // The registered command
 356          ret.command = *(it++);
 357      }
 358      while (it != m_command.end()) {
 359          // The unregistered command and args (if any)
 360          ret.args.push_back(*(it++));
 361      }
 362      return ret;
 363  }
 364  
 365  std::vector<std::string> ArgsManager::GetArgs(const std::string& strArg) const
 366  {
 367      std::vector<std::string> result;
 368      for (const common::SettingsValue& value : GetSettingsList(strArg)) {
 369          result.push_back(value.isFalse() ? "0" : value.isTrue() ? "1" : value.get_str());
 370      }
 371      return result;
 372  }
 373  
 374  bool ArgsManager::IsArgSet(const std::string& strArg) const
 375  {
 376      return !GetSetting(strArg).isNull();
 377  }
 378  
 379  bool ArgsManager::GetSettingsPath(fs::path* filepath, bool temp, bool backup) const
 380  {
 381      fs::path settings = GetPathArg("-settings", LIMENKA_SETTINGS_FILENAME);
 382      if (settings.empty()) {
 383          return false;
 384      }
 385      if (backup) {
 386          settings += ".bak";
 387      }
 388      if (filepath) {
 389          *filepath = fsbridge::AbsPathJoin(GetDataDirNet(), temp ? settings + ".tmp" : settings);
 390      }
 391      return true;
 392  }
 393  
 394  static void SaveErrors(const std::vector<std::string> errors, std::vector<std::string>* error_out)
 395  {
 396      for (const auto& error : errors) {
 397          if (error_out) {
 398              error_out->emplace_back(error);
 399          } else {
 400              LogWarning("%s", error);
 401          }
 402      }
 403  }
 404  
 405  bool ArgsManager::ReadSettingsFile(std::vector<std::string>* errors)
 406  {
 407      fs::path path;
 408      if (!GetSettingsPath(&path, /* temp= */ false)) {
 409          return true; // Do nothing if settings file disabled.
 410      }
 411  
 412      LOCK(cs_args);
 413      m_settings.rw_settings.clear();
 414      std::vector<std::string> read_errors;
 415      if (!common::ReadSettings(path, m_settings.rw_settings, read_errors)) {
 416          SaveErrors(read_errors, errors);
 417          return false;
 418      }
 419      for (const auto& setting : m_settings.rw_settings) {
 420          KeyInfo key = InterpretKey(setting.first); // Split setting key into section and argname
 421          if (!GetArgFlags('-' + key.name)) {
 422              LogWarning("Ignoring unknown rw_settings value %s", setting.first);
 423          }
 424      }
 425      return true;
 426  }
 427  
 428  bool ArgsManager::WriteSettingsFile(std::vector<std::string>* errors, bool backup) const
 429  {
 430      fs::path path, path_tmp;
 431      if (!GetSettingsPath(&path, /*temp=*/false, backup) || !GetSettingsPath(&path_tmp, /*temp=*/true, backup)) {
 432          throw std::logic_error("Attempt to write settings file when dynamic settings are disabled.");
 433      }
 434  
 435      LOCK(cs_args);
 436      std::vector<std::string> write_errors;
 437      if (!common::WriteSettings(path_tmp, m_settings.rw_settings, write_errors)) {
 438          SaveErrors(write_errors, errors);
 439          return false;
 440      }
 441      if (!RenameOver(path_tmp, path)) {
 442          SaveErrors({strprintf("Failed renaming settings file %s to %s\n", fs::PathToString(path_tmp), fs::PathToString(path))}, errors);
 443          return false;
 444      }
 445      return true;
 446  }
 447  
 448  common::SettingsValue ArgsManager::GetPersistentSetting(const std::string& name) const
 449  {
 450      LOCK(cs_args);
 451      return common::GetSetting(m_settings, m_network, name, !UseDefaultSection("-" + name),
 452          /*ignore_nonpersistent=*/true, /*get_chain_type=*/false);
 453  }
 454  
 455  bool ArgsManager::IsArgNegated(const std::string& strArg) const
 456  {
 457      return GetSetting(strArg).isFalse();
 458  }
 459  
 460  std::string ArgsManager::GetArg(const std::string& strArg, const std::string& strDefault) const
 461  {
 462      return GetArg(strArg).value_or(strDefault);
 463  }
 464  
 465  std::optional<std::string> ArgsManager::GetArg(const std::string& strArg) const
 466  {
 467      const common::SettingsValue value = GetSetting(strArg);
 468      return SettingToString(value);
 469  }
 470  
 471  std::optional<std::string> SettingToString(const common::SettingsValue& value)
 472  {
 473      if (value.isNull()) return std::nullopt;
 474      if (value.isFalse()) return "0";
 475      if (value.isTrue()) return "1";
 476      if (value.isNum()) return value.getValStr();
 477      return value.get_str();
 478  }
 479  
 480  std::string SettingToString(const common::SettingsValue& value, const std::string& strDefault)
 481  {
 482      return SettingToString(value).value_or(strDefault);
 483  }
 484  
 485  int64_t ArgsManager::GetIntArg(const std::string& strArg, int64_t nDefault) const
 486  {
 487      return GetIntArg(strArg).value_or(nDefault);
 488  }
 489  
 490  std::optional<int64_t> ArgsManager::GetIntArg(const std::string& strArg) const
 491  {
 492      const common::SettingsValue value = GetSetting(strArg);
 493      return SettingToInt(value);
 494  }
 495  
 496  std::optional<int64_t> SettingToInt(const common::SettingsValue& value)
 497  {
 498      if (value.isNull()) return std::nullopt;
 499      if (value.isFalse()) return 0;
 500      if (value.isTrue()) return 1;
 501      if (value.isNum()) return value.getInt<int64_t>();
 502      return LocaleIndependentAtoi<int64_t>(value.get_str());
 503  }
 504  
 505  int64_t SettingToInt(const common::SettingsValue& value, int64_t nDefault)
 506  {
 507      return SettingToInt(value).value_or(nDefault);
 508  }
 509  
 510  std::optional<int64_t> ArgsManager::GetFixedPointArg(const std::string& arg, int decimals) const
 511  {
 512      const common::SettingsValue value = GetSetting(arg);
 513      return SettingToFixedPoint(value, decimals);
 514  }
 515  
 516  std::optional<int64_t> SettingToFixedPoint(const common::SettingsValue& value, int decimals)
 517  {
 518      if (value.isNull()) return std::nullopt;
 519      if (value.isFalse()) return 0;
 520      if (value.isTrue()) return 1;
 521      if (!value.isNum()) value.get_str();  // throws an exception if type is wrong
 522      int64_t v;
 523      if (!ParseFixedPoint(value.getValStr(), decimals, &v)) {
 524          throw std::runtime_error(strprintf("Parse error ('%s')", value.getValStr()));
 525      }
 526      return v;
 527  }
 528  
 529  bool ArgsManager::GetBoolArg(const std::string& strArg, bool fDefault) const
 530  {
 531      return GetBoolArg(strArg).value_or(fDefault);
 532  }
 533  
 534  std::optional<bool> ArgsManager::GetBoolArg(const std::string& strArg) const
 535  {
 536      const common::SettingsValue value = GetSetting(strArg);
 537      return SettingToBool(value);
 538  }
 539  
 540  std::optional<bool> SettingToBool(const common::SettingsValue& value)
 541  {
 542      switch (value.getType()) {
 543          case UniValue::VNULL:
 544              return std::nullopt;
 545          case UniValue::VBOOL:
 546              return value.get_bool();
 547          case UniValue::VOBJ:
 548          case UniValue::VARR:
 549              // Throws an exception
 550              value.get_str();
 551              assert(false);
 552          case UniValue::VSTR:
 553          case UniValue::VNUM:
 554              return InterpretBool(value.getValStr());
 555      }
 556      assert(false);
 557  }
 558  
 559  bool SettingToBool(const common::SettingsValue& value, bool fDefault)
 560  {
 561      return SettingToBool(value).value_or(fDefault);
 562  }
 563  
 564  bool ArgsManager::SoftSetArg(const std::string& strArg, const std::string& strValue)
 565  {
 566      LOCK(cs_args);
 567      if (IsArgSet(strArg)) return false;
 568      ForceSetArg(strArg, strValue);
 569      return true;
 570  }
 571  
 572  bool ArgsManager::SoftSetBoolArg(const std::string& strArg, bool fValue)
 573  {
 574      if (fValue)
 575          return SoftSetArg(strArg, std::string("1"));
 576      else
 577          return SoftSetArg(strArg, std::string("0"));
 578  }
 579  
 580  void ArgsManager::ForceSetArg(const std::string& strArg, const std::string& strValue)
 581  {
 582      ForceSetArgV(strArg, common::SettingsValue{strValue});
 583  }
 584  
 585  void ArgsManager::ForceSetArg(const std::string& arg, const int64_t value)
 586  {
 587      ForceSetArg(arg, util::ToString(value));
 588  }
 589  
 590  void ArgsManager::ForceSetArgV(const std::string& arg, const common::SettingsValue& value)
 591  {
 592      LOCK(cs_args);
 593      m_settings.forced_settings[SettingName(arg)] = value;
 594  }
 595  
 596  void ArgsManager::AddCommand(const std::string& cmd, const std::string& help)
 597  {
 598      Assert(cmd.find('=') == std::string::npos);
 599      Assert(cmd.at(0) != '-');
 600  
 601      LOCK(cs_args);
 602      m_accept_any_command = false; // latch to false
 603      std::map<std::string, Arg>& arg_map = m_available_args[OptionsCategory::COMMANDS];
 604      auto ret = arg_map.emplace(cmd, Arg{"", help, ArgsManager::COMMAND});
 605      Assert(ret.second); // Fail on duplicate commands
 606  }
 607  
 608  void ArgsManager::AddArg(const std::string& name, const std::string& help, unsigned int flags, const OptionsCategory& cat)
 609  {
 610      Assert((flags & ArgsManager::COMMAND) == 0); // use AddCommand
 611  
 612      // Split arg name from its help param
 613      size_t eq_index = name.find('=');
 614      if (eq_index == std::string::npos) {
 615          eq_index = name.size();
 616      }
 617      std::string arg_name = name.substr(0, eq_index);
 618  
 619      LOCK(cs_args);
 620      std::map<std::string, Arg>& arg_map = m_available_args[cat];
 621      auto ret = arg_map.emplace(arg_name, Arg{name.substr(eq_index, name.size() - eq_index), help, flags});
 622      assert(ret.second); // Make sure an insertion actually happened
 623  
 624      if (flags & ArgsManager::NETWORK_ONLY) {
 625          m_network_only_args.emplace(arg_name);
 626      }
 627  }
 628  
 629  void ArgsManager::AddHiddenArgs(const std::vector<std::string>& names, unsigned int flags)
 630  {
 631      for (const std::string& name : names) {
 632          AddArg(name, "", flags, OptionsCategory::HIDDEN);
 633      }
 634  }
 635  
 636  void ArgsManager::CheckMultipleCLIArgs() const
 637  {
 638      LOCK(cs_args);
 639      std::vector<std::string> found{};
 640      auto cmds = m_available_args.find(OptionsCategory::CLI_COMMANDS);
 641      if (cmds != m_available_args.end()) {
 642          for (const auto& [cmd, argspec] : cmds->second) {
 643              if (IsArgSet(cmd)) {
 644                  found.push_back(cmd);
 645              }
 646          }
 647          if (found.size() > 1) {
 648              throw std::runtime_error(strprintf("Only one of %s may be specified.", util::Join(found, ", ")));
 649          }
 650      }
 651  }
 652  
 653  std::string ArgsManager::GetHelpMessage() const
 654  {
 655      const bool show_debug = GetBoolArg("-help-debug", false);
 656  
 657      std::string usage;
 658      LOCK(cs_args);
 659      for (const auto& arg_map : m_available_args) {
 660          switch(arg_map.first) {
 661              case OptionsCategory::OPTIONS:
 662                  usage += HelpMessageGroup("Options:");
 663                  break;
 664              case OptionsCategory::CONNECTION:
 665                  usage += HelpMessageGroup("Connection options:");
 666                  break;
 667              case OptionsCategory::ZMQ:
 668                  usage += HelpMessageGroup("ZeroMQ notification options:");
 669                  break;
 670              case OptionsCategory::DEBUG_TEST:
 671                  usage += HelpMessageGroup("Debugging/Testing options:");
 672                  break;
 673              case OptionsCategory::NODE_RELAY:
 674                  usage += HelpMessageGroup("Node relay options:");
 675                  break;
 676              case OptionsCategory::BLOCK_CREATION:
 677                  usage += HelpMessageGroup("Block creation options:");
 678                  break;
 679              case OptionsCategory::RPC:
 680                  usage += HelpMessageGroup("RPC server options:");
 681                  break;
 682              case OptionsCategory::IPC:
 683                  usage += HelpMessageGroup("IPC interprocess connection options:");
 684                  break;
 685              case OptionsCategory::WALLET:
 686                  usage += HelpMessageGroup("Wallet options:");
 687                  break;
 688              case OptionsCategory::WALLET_DEBUG_TEST:
 689                  if (show_debug) usage += HelpMessageGroup("Wallet debugging/testing options:");
 690                  break;
 691              case OptionsCategory::CHAINPARAMS:
 692                  usage += HelpMessageGroup("Chain selection options:");
 693                  break;
 694              case OptionsCategory::GUI:
 695                  usage += HelpMessageGroup("UI Options:");
 696                  break;
 697              case OptionsCategory::COMMANDS:
 698                  usage += HelpMessageGroup("Commands:");
 699                  break;
 700              case OptionsCategory::REGISTER_COMMANDS:
 701                  usage += HelpMessageGroup("Register Commands:");
 702                  break;
 703              case OptionsCategory::CLI_COMMANDS:
 704                  usage += HelpMessageGroup("CLI Commands:");
 705                  break;
 706              case OptionsCategory::STATS:
 707                  usage += HelpMessageGroup("Statistic options:");
 708                  break;
 709              default:
 710                  break;
 711          }
 712  
 713          // When we get to the hidden options, stop
 714          if (arg_map.first == OptionsCategory::HIDDEN) break;
 715  
 716          for (const auto& arg : arg_map.second) {
 717              if (show_debug || !(arg.second.m_flags & ArgsManager::DEBUG_ONLY)) {
 718                  std::string name;
 719                  if (arg.second.m_help_param.empty()) {
 720                      name = arg.first;
 721                  } else {
 722                      name = arg.first + arg.second.m_help_param;
 723                  }
 724                  usage += HelpMessageOpt(name, arg.second.m_help_text);
 725              }
 726          }
 727      }
 728      return usage;
 729  }
 730  
 731  bool HelpRequested(const ArgsManager& args)
 732  {
 733      return args.IsArgSet("-?") || args.IsArgSet("-h") || args.IsArgSet("-help") || args.IsArgSet("-help-debug");
 734  }
 735  
 736  void SetupHelpOptions(ArgsManager& args)
 737  {
 738      args.AddArg("-help", "Print this help message and exit (also -h or -?)", ArgsManager::DISALLOW_NEGATION, OptionsCategory::OPTIONS);
 739      args.AddHiddenArgs({"-h", "-?"}, ArgsManager::DISALLOW_NEGATION);
 740  }
 741  
 742  static const int screenWidth = 79;
 743  static const int optIndent = 2;
 744  static const int msgIndent = 7;
 745  
 746  std::string HelpMessageGroup(const std::string &message) {
 747      return std::string(message) + std::string("\n\n");
 748  }
 749  
 750  std::string HelpMessageOpt(const std::string &option, const std::string &message) {
 751      return std::string(optIndent,' ') + std::string(option) +
 752             std::string("\n") + std::string(msgIndent,' ') +
 753             FormatParagraph(message, screenWidth - msgIndent, msgIndent) +
 754             std::string("\n\n");
 755  }
 756  
 757  const std::vector<std::string> TEST_OPTIONS_DOC{
 758      "addrman (use deterministic addrman)",
 759      "reindex_after_failure_noninteractive_yes (When asked for a reindex after failure interactively, simulate as-if answered with 'yes')",
 760      "bip94 (enforce BIP94 consensus rules)",
 761  };
 762  
 763  bool HasTestOption(const ArgsManager& args, const std::string& test_option)
 764  {
 765      const auto options = args.GetArgs("-test");
 766      return std::any_of(options.begin(), options.end(), [test_option](const auto& option) {
 767          return option == test_option;
 768      });
 769  }
 770  
 771  fs::path GetDefaultDataDir()
 772  {
 773      // Windows:
 774      //   old: C:\Users\Username\AppData\Roaming\Limenka
 775      //   new: C:\Users\Username\AppData\Local\Limenka
 776      // macOS: ~/Library/Application Support/Limenka
 777      // Unix-like: ~/.limenka
 778  #ifdef WIN32
 779      // Windows
 780      // Check for existence of datadir in old location and keep it there
 781      fs::path legacy_path = GetSpecialFolderPath(CSIDL_APPDATA) / "Limenka";
 782      if (fs::exists(legacy_path)) return legacy_path;
 783  
 784      // Otherwise, fresh installs can start in the new, "proper" location
 785      return GetSpecialFolderPath(CSIDL_LOCAL_APPDATA) / "Limenka";
 786  #else
 787      fs::path pathRet;
 788      char* pszHome = getenv("HOME");
 789      if (pszHome == nullptr || strlen(pszHome) == 0)
 790          pathRet = fs::path("/");
 791      else
 792          pathRet = fs::path(pszHome);
 793  #ifdef __APPLE__
 794      // macOS
 795      return pathRet / "Library/Application Support/Limenka";
 796  #else
 797      // Unix-like
 798      return pathRet / ".limenka";
 799  #endif
 800  #endif
 801  }
 802  
 803  bool CheckDataDirOption(const ArgsManager& args)
 804  {
 805      const fs::path datadir{args.GetPathArg("-datadir")};
 806      return datadir.empty() || fs::is_directory(fs::absolute(datadir));
 807  }
 808  
 809  fs::path ArgsManager::GetConfigFilePath() const
 810  {
 811      LOCK(cs_args);
 812      return *Assert(m_config_path);
 813  }
 814  
 815  void ArgsManager::SetConfigFilePath(fs::path path)
 816  {
 817      LOCK(cs_args);
 818      assert(!m_config_path);
 819      m_config_path = path;
 820  }
 821  
 822  fs::path ArgsManager::GetRWConfigFilePath() const
 823  {
 824      LOCK(cs_args);
 825      return *Assert(m_rwconf_path);
 826  }
 827  
 828  ChainType ArgsManager::GetChainType() const
 829  {
 830      std::variant<ChainType, std::string> arg = GetChainArg();
 831      if (auto* parsed = std::get_if<ChainType>(&arg)) return *parsed;
 832      throw std::runtime_error(strprintf("Unknown chain %s.", std::get<std::string>(arg)));
 833  }
 834  
 835  std::string ArgsManager::GetChainTypeString() const
 836  {
 837      auto arg = GetChainArg();
 838      if (auto* parsed = std::get_if<ChainType>(&arg)) return ChainTypeToString(*parsed);
 839      return std::get<std::string>(arg);
 840  }
 841  
 842  std::variant<ChainType, std::string> ArgsManager::GetChainArg() const
 843  {
 844      auto get_net = [&](const std::string& arg) {
 845          LOCK(cs_args);
 846          common::SettingsValue value = common::GetSetting(m_settings, /* section= */ "", SettingName(arg),
 847              /* ignore_default_section_config= */ false,
 848              /*ignore_nonpersistent=*/false,
 849              /* get_chain_type= */ true);
 850          return value.isNull() ? false : value.isBool() ? value.get_bool() : InterpretBool(value.get_str());
 851      };
 852  
 853      const bool fRegTest = get_net("-regtest");
 854      const bool fSigNet  = get_net("-signet");
 855      const bool fTestNet = get_net("-testnet");
 856      const bool fTestNet4 = get_net("-testnet4");
 857      const bool fFork    = get_net("-limenka");
 858      const auto chain_arg = GetArg("-chain");
 859  
 860      if ((int)chain_arg.has_value() + (int)fRegTest + (int)fSigNet + (int)fTestNet + (int)fTestNet4 + (int)fFork > 1) {
 861          throw std::runtime_error("Invalid combination of -regtest, -signet, -testnet, -testnet4, -limenka and -chain. Can use at most one.");
 862      }
 863      if (chain_arg) {
 864          if (auto parsed = ChainTypeFromString(*chain_arg)) return *parsed;
 865          return *chain_arg;
 866      }
 867      if (fRegTest) return ChainType::REGTEST;
 868      if (fSigNet) return ChainType::SIGNET;
 869      if (fTestNet) return ChainType::TESTNET;
 870      if (fTestNet4) return ChainType::TESTNET4;
 871      if (fFork) return ChainType::FORK;
 872      return ChainType::MAIN;
 873  }
 874  
 875  bool ArgsManager::UseDefaultSection(const std::string& arg) const
 876  {
 877      return m_network == ChainTypeToString(ChainType::MAIN) || m_network_only_args.count(arg) == 0;
 878  }
 879  
 880  common::SettingsValue ArgsManager::GetSetting(const std::string& arg) const
 881  {
 882      LOCK(cs_args);
 883      return common::GetSetting(
 884          m_settings, m_network, SettingName(arg), !UseDefaultSection(arg),
 885          /*ignore_nonpersistent=*/false, /*get_chain_type=*/false);
 886  }
 887  
 888  std::vector<common::SettingsValue> ArgsManager::GetSettingsList(const std::string& arg) const
 889  {
 890      LOCK(cs_args);
 891      return common::GetSettingsList(m_settings, m_network, SettingName(arg), !UseDefaultSection(arg));
 892  }
 893  
 894  void ArgsManager::logArgsPrefix(
 895      const std::string& prefix,
 896      const std::string& section,
 897      const std::map<std::string, std::vector<common::SettingsValue>>& args) const
 898  {
 899      std::string section_str = section.empty() ? "" : "[" + section + "] ";
 900      for (const auto& arg : args) {
 901          for (const auto& value : arg.second) {
 902              std::optional<unsigned int> flags = GetArgFlags('-' + arg.first);
 903              if (flags) {
 904                  std::string value_str = (*flags & SENSITIVE) ? "****" : value.write();
 905                  LogPrintf("%s %s%s=%s\n", prefix, section_str, arg.first, value_str);
 906              }
 907          }
 908      }
 909  }
 910  
 911  void ArgsManager::LogArgs() const
 912  {
 913      LOCK(cs_args);
 914      for (const auto& section : m_settings.ro_config) {
 915          logArgsPrefix("Config file arg:", section.first, section.second);
 916      }
 917      for (const auto& setting : m_settings.rw_settings) {
 918          LogPrintf("Setting file arg: %s = %s\n", setting.first, setting.second.write());
 919      }
 920      logArgsPrefix("R/W config file arg:", "", m_settings.rw_config);
 921      logArgsPrefix("Command-line arg:", "", m_settings.command_line_options);
 922  }
 923  
 924  namespace {
 925  
 926      // Like std::getline, but includes the EOL character in the result
 927      bool getline_with_eol(std::istream& stream, std::string& result)
 928      {
 929          int current_char;
 930          current_char = stream.get();
 931          if (current_char == std::char_traits<char>::eof()) {
 932              return false;
 933          }
 934          result.clear();
 935          result.push_back(char(current_char));
 936          while (current_char != '\n') {
 937              current_char = stream.get();
 938              if (current_char == std::char_traits<char>::eof()) {
 939                  break;
 940              }
 941              result.push_back(char(current_char));
 942          }
 943          return true;
 944      }
 945  
 946      const char * const ModifyRWConfigFile_ws_chars = " \t\r\n";
 947  
 948      void ModifyRWConfigFile_SanityCheck(const std::string& s)
 949      {
 950          if (s.empty()) {
 951              // Dereferencing .begin or .rbegin below is invalid unless the string has at least one character.
 952              return;
 953          }
 954  
 955          static const char * const newline_chars = "\r\n";
 956          static std::string ws_chars(ModifyRWConfigFile_ws_chars);
 957          if (s.find_first_of(newline_chars) != std::string::npos) {
 958              throw std::invalid_argument("New-line in config name/value");
 959          }
 960          if (ws_chars.find(*s.begin()) != std::string::npos || ws_chars.find(*s.rbegin()) != std::string::npos) {
 961              throw std::invalid_argument("Config name/value has leading/trailing whitespace");
 962          }
 963      }
 964  
 965      void ModifyRWConfigFile_WriteRemaining(std::ostream& stream_out, const std::map<std::string, std::string>& settings_to_change, std::set<std::string>& setFound)
 966      {
 967          for (const auto& setting_pair : settings_to_change) {
 968              const std::string& key = setting_pair.first;
 969              const std::string& val = setting_pair.second;
 970              if (setFound.find(key) != setFound.end()) {
 971                  continue;
 972              }
 973              setFound.insert(key);
 974              ModifyRWConfigFile_SanityCheck(key);
 975              ModifyRWConfigFile_SanityCheck(val);
 976              stream_out << key << "=" << val << "\n";
 977          }
 978      }
 979  } // namespace
 980  
 981  void ModifyRWConfigStream(std::istream& stream_in, std::ostream& stream_out, const std::map<std::string, std::string>& settings_to_change)
 982  {
 983      static const char * const ws_chars = ModifyRWConfigFile_ws_chars;
 984      std::set<std::string> setFound;
 985      std::string s, lineend, linebegin, key;
 986      std::string::size_type n, n2;
 987      bool inside_group = false, have_eof_nl = true;
 988      std::map<std::string, std::string>::const_iterator iterCS;
 989      size_t lineno = 0;
 990      while (getline_with_eol(stream_in, s)) {
 991          ++lineno;
 992  
 993          have_eof_nl = (!s.empty()) && (*s.rbegin() == '\n');
 994          n = s.find('#');
 995          const bool has_comment = (n != std::string::npos);
 996          if (!has_comment) {
 997              n = s.size();
 998          }
 999          if (n > 0) {
1000              n2 = s.find_last_not_of(ws_chars, n - 1);
1001              if (n2 != std::string::npos) {
1002                  n = n2 + 1;
1003              }
1004          }
1005          n2 = s.find_first_not_of(ws_chars);
1006          if (n2 == std::string::npos || n2 >= n) {
1007              // Blank or comment-only line
1008              stream_out << s;
1009              continue;
1010          }
1011          lineend = s.substr(n);
1012          linebegin = s.substr(0, n2);
1013          s = s.substr(n2, n - n2);
1014  
1015          // It is impossible for s to be empty here, due to the blank line check above
1016          if (*s.begin() == '[' && *s.rbegin() == ']') {
1017              // We don't use sections, so we could possibly just write out the rest of the file - but we need to check for unparsable lines, so we just set a flag to ignore settings from here on
1018              ModifyRWConfigFile_WriteRemaining(stream_out, settings_to_change, setFound);
1019              inside_group = true;
1020              key.clear();
1021  
1022              stream_out << linebegin << s << lineend;
1023              continue;
1024          }
1025  
1026          n = s.find('=');
1027          if (n == std::string::npos) {
1028              // Bad line; this causes boost to throw an exception when parsing, so we comment out the entire file
1029              stream_in.seekg(0, std::ios_base::beg);
1030              stream_out.seekp(0, std::ios_base::beg);
1031              if (!(stream_in.good() && stream_out.good())) {
1032                  throw std::ios_base::failure("Failed to rewind (to comment out existing file)");
1033              }
1034              // First, write out all the settings we intend to set
1035              setFound.clear();
1036              ModifyRWConfigFile_WriteRemaining(stream_out, settings_to_change, setFound);
1037              // We then define a category to ensure new settings get added before the invalid stuff
1038              stream_out << "[INVALID]\n";
1039              // Then, describe the problem in a comment
1040              stream_out << "# Error parsing line " << lineno << ": " << s << "\n";
1041              // Finally, dump the rest of the file commented out
1042              while (getline_with_eol(stream_in, s)) {
1043                  stream_out << "#" << s;
1044              }
1045              return;
1046          }
1047  
1048          if (!inside_group) {
1049              // We don't support/use groups, so once we're inside key is always null to avoid setting anything
1050              n2 = s.find_last_not_of(ws_chars, n - 1);
1051              if (n2 == std::string::npos) {
1052                  n2 = n - 1;
1053              } else {
1054                  ++n2;
1055              }
1056              key = s.substr(0, n2);
1057          }
1058          if ((!key.empty()) && (iterCS = settings_to_change.find(key)) != settings_to_change.end() && setFound.find(key) == setFound.end()) {
1059              // This is the key we want to change
1060              const std::string& val = iterCS->second;
1061              setFound.insert(key);
1062              ModifyRWConfigFile_SanityCheck(val);
1063              if (has_comment) {
1064                  // Rather than change a commented line, comment it out entirely (the existing comment may relate to the value) and replace it
1065                  stream_out << key << "=" << val << "\n";
1066                  linebegin.insert(linebegin.begin(), '#');
1067              } else {
1068                  // Just modify the value in-line otherwise
1069                  n2 = s.find_first_not_of(ws_chars, n + 1);
1070                  if (n2 == std::string::npos) {
1071                      n2 = n + 1;
1072                  }
1073                  s = s.substr(0, n2) + val;
1074              }
1075          }
1076          stream_out << linebegin << s << lineend;
1077      }
1078      if (setFound.size() < settings_to_change.size()) {
1079          if (!have_eof_nl) {
1080              stream_out << "\n";
1081          }
1082          ModifyRWConfigFile_WriteRemaining(stream_out, settings_to_change, setFound);
1083      }
1084  }
1085  
1086  void ArgsManager::ModifyRWConfigFile(const std::map<std::string, std::string>& settings_to_change, const bool also_settings_json)
1087  {
1088      LOCK(cs_args);
1089      fs::path rwconf_path{GetRWConfigFilePath()};
1090      fs::path rwconf_new_path{rwconf_path};
1091      rwconf_new_path += ".new";
1092      try {
1093          fs::remove(rwconf_new_path);
1094          std::ofstream streamRWConfigOut(rwconf_new_path, std::ios_base::out | std::ios_base::trunc);
1095          if (fs::exists(rwconf_path)) {
1096              std::ifstream streamRWConfig(rwconf_path);
1097              ::ModifyRWConfigStream(streamRWConfig, streamRWConfigOut, settings_to_change);
1098          } else {
1099              std::istringstream streamIn;
1100              ::ModifyRWConfigStream(streamIn, streamRWConfigOut, settings_to_change);
1101          }
1102      } catch (...) {
1103          fs::remove(rwconf_new_path);
1104          throw;
1105      }
1106      if (!RenameOver(rwconf_new_path, rwconf_path)) {
1107          fs::remove(rwconf_new_path);
1108          throw std::ios_base::failure(strprintf("Failed to replace %s", fs::PathToString(rwconf_new_path)));
1109      }
1110      for (const auto& setting_change : settings_to_change) {
1111          m_settings.rw_config[setting_change.first] = {setting_change.second};
1112      }
1113      if (also_settings_json && !IsArgNegated("-settings")) {
1114          // Also save to settings.json for Core (0.21+) compatibility
1115          for (const auto& setting_change : settings_to_change) {
1116              m_settings.rw_settings[setting_change.first] = setting_change.second;
1117          }
1118          WriteSettingsFile();
1119      }
1120      if (settings_to_change.count("prune")) {
1121          m_rwconf_had_prune_option = true;
1122      }
1123  }
1124  
1125  void ArgsManager::ModifyRWConfigFile(const std::string& setting_to_change, const std::string& new_value, const bool also_settings_json)
1126  {
1127      std::map<std::string, std::string> settings_to_change;
1128      settings_to_change[setting_to_change] = new_value;
1129      ModifyRWConfigFile(settings_to_change, also_settings_json);
1130  }
1131  
1132  void ArgsManager::EraseRWConfigFile()
1133  {
1134      LOCK(cs_args);
1135      fs::path rwconf_path{GetRWConfigFilePath()};
1136      if (!fs::exists(rwconf_path)) {
1137          return;
1138      }
1139      fs::path rwconf_reset_path = rwconf_path;
1140      rwconf_reset_path += ".reset";
1141      if (!RenameOver(rwconf_path, rwconf_reset_path)) {
1142          if (fs::remove(rwconf_path)) {
1143              throw std::ios_base::failure(strprintf("Failed to remove %s", fs::PathToString(rwconf_path)));
1144          }
1145      }
1146  }
1147  
1148  namespace common {
1149  #ifdef WIN32
1150  WinCmdLineArgs::WinCmdLineArgs()
1151  {
1152      wchar_t** wargv = CommandLineToArgvW(GetCommandLineW(), &argc);
1153      std::wstring_convert<std::codecvt_utf8_utf16<wchar_t>, wchar_t> utf8_cvt;
1154      argv = new char*[argc];
1155      args.resize(argc);
1156      for (int i = 0; i < argc; i++) {
1157          args[i] = utf8_cvt.to_bytes(wargv[i]);
1158          argv[i] = &*args[i].begin();
1159      }
1160      LocalFree(wargv);
1161  }
1162  
1163  WinCmdLineArgs::~WinCmdLineArgs()
1164  {
1165      delete[] argv;
1166  }
1167  
1168  std::pair<int, char**> WinCmdLineArgs::get()
1169  {
1170      return std::make_pair(argc, argv);
1171  }
1172  #endif
1173  } // namespace common
1174