settings.cpp raw

   1  // Copyright (c) 2019-present The Bitcoin Core developers
   2  // Distributed under the MIT software license, see the accompanying
   3  // file COPYING or http://www.opensource.org/licenses/mit-license.php.
   4  
   5  #include <common/settings.h>
   6  
   7  #include <bitcoin-build-config.h> // IWYU pragma: keep
   8  
   9  #include <tinyformat.h>
  10  #include <univalue.h>
  11  #include <util/fs.h>
  12  
  13  #include <algorithm>
  14  #include <fstream>
  15  #include <iterator>
  16  #include <map>
  17  #include <string>
  18  #include <utility>
  19  #include <vector>
  20  
  21  namespace common {
  22  namespace {
  23  
  24  enum class Source {
  25     FORCED,
  26     COMMAND_LINE,
  27     RW_SETTINGS,
  28     CONFIG_FILE_NETWORK_SECTION,
  29     CONFIG_FILE_DEFAULT_SECTION
  30  };
  31  
  32  // Json object key for the auto-generated warning comment
  33  const std::string SETTINGS_WARN_MSG_KEY{"_warning_"};
  34  
  35  //! Merge settings from multiple sources in precedence order:
  36  //! Forced config > command line > read-write settings file > config file network-specific section > config file default section
  37  //!
  38  //! This function is provided with a callback function fn that contains
  39  //! specific logic for how to merge the sources.
  40  template <typename Fn>
  41  static void MergeSettings(const Settings& settings, const std::string& section, const std::string& name, Fn&& fn)
  42  {
  43      // Merge in the forced settings
  44      if (auto* value = FindKey(settings.forced_settings, name)) {
  45          fn(SettingsSpan(*value), Source::FORCED);
  46      }
  47      // Merge in the command-line options
  48      if (auto* values = FindKey(settings.command_line_options, name)) {
  49          fn(SettingsSpan(*values), Source::COMMAND_LINE);
  50      }
  51      // Merge in the read-write settings
  52      if (const SettingsValue* value = FindKey(settings.rw_settings, name)) {
  53          fn(SettingsSpan(*value), Source::RW_SETTINGS);
  54      }
  55      // Merge in the network-specific section of the config file
  56      if (!section.empty()) {
  57          if (auto* map = FindKey(settings.ro_config, section)) {
  58              if (auto* values = FindKey(*map, name)) {
  59                  fn(SettingsSpan(*values), Source::CONFIG_FILE_NETWORK_SECTION);
  60              }
  61          }
  62      }
  63      // Merge in the default section of the config file
  64      if (auto* map = FindKey(settings.ro_config, "")) {
  65          if (auto* values = FindKey(*map, name)) {
  66              fn(SettingsSpan(*values), Source::CONFIG_FILE_DEFAULT_SECTION);
  67          }
  68      }
  69  }
  70  } // namespace
  71  
  72  bool ReadSettings(const fs::path& path, std::map<std::string, SettingsValue>& values, std::vector<std::string>& errors)
  73  {
  74      values.clear();
  75      errors.clear();
  76  
  77      // Ok for file to not exist
  78      if (!fs::exists(path)) return true;
  79  
  80      std::ifstream file;
  81      file.open(path.std_path());
  82      if (!file.is_open()) {
  83        errors.emplace_back(strprintf("%s. Please check permissions.", fs::PathToString(path)));
  84        return false;
  85      }
  86  
  87      SettingsValue in;
  88      if (!in.read(std::string{std::istreambuf_iterator<char>(file), std::istreambuf_iterator<char>()})) {
  89          errors.emplace_back(strprintf("Settings file %s does not contain valid JSON. This may be caused by a crash, power loss, full disk, or storage error, "
  90                                        "and can be fixed by removing the file, which will reset settings to default values.",
  91                                        fs::PathToString(path)));
  92          return false;
  93      }
  94  
  95      if (file.fail()) {
  96          errors.emplace_back(strprintf("Failed reading settings file %s", fs::PathToString(path)));
  97          return false;
  98      }
  99      file.close(); // Done with file descriptor. Release while copying data.
 100  
 101      if (!in.isObject()) {
 102          errors.emplace_back(strprintf("Found non-object value %s in settings file %s", in.write(), fs::PathToString(path)));
 103          return false;
 104      }
 105  
 106      const std::vector<std::string>& in_keys = in.getKeys();
 107      const std::vector<SettingsValue>& in_values = in.getValues();
 108      for (size_t i = 0; i < in_keys.size(); ++i) {
 109          auto inserted = values.emplace(in_keys[i], in_values[i]);
 110          if (!inserted.second) {
 111              errors.emplace_back(strprintf("Found duplicate key %s in settings file %s", in_keys[i], fs::PathToString(path)));
 112              values.clear();
 113              break;
 114          }
 115      }
 116  
 117      // Remove auto-generated warning comment from the accessible settings.
 118      values.erase(SETTINGS_WARN_MSG_KEY);
 119  
 120      return errors.empty();
 121  }
 122  
 123  bool WriteSettings(const fs::path& path,
 124      const std::map<std::string, SettingsValue>& values,
 125      std::vector<std::string>& errors)
 126  {
 127      SettingsValue out(SettingsValue::VOBJ);
 128      // Add auto-generated warning comment
 129      out.pushKV(SETTINGS_WARN_MSG_KEY, strprintf("This file is automatically generated and updated by %s. Please do not edit this file while the node "
 130                                                  "is running, as any changes might be ignored or overwritten.", CLIENT_NAME));
 131      // Push settings values
 132      for (const auto& value : values) {
 133          out.pushKVEnd(value.first, value.second);
 134      }
 135      std::ofstream file;
 136      file.open(path.std_path());
 137      if (file.fail()) {
 138          errors.emplace_back(strprintf("Error: Unable to open settings file %s for writing", fs::PathToString(path)));
 139          return false;
 140      }
 141      file << out.write(/* prettyIndent= */ 4, /* indentLevel= */ 1) << std::endl;
 142      if (file.fail()) {
 143          errors.emplace_back(strprintf("Error: Unable to write settings file %s", fs::PathToString(path)));
 144          return false;
 145      }
 146      file.close();
 147      if (file.fail()) {
 148          errors.emplace_back(strprintf("Error: Unable to close settings file %s", fs::PathToString(path)));
 149          return false;
 150      }
 151      return true;
 152  }
 153  
 154  SettingsValue GetSetting(const Settings& settings,
 155      const std::string& section,
 156      const std::string& name,
 157      bool ignore_default_section_config,
 158      bool ignore_nonpersistent,
 159      bool get_chain_type)
 160  {
 161      SettingsValue result;
 162      bool done = false; // Done merging any more settings sources.
 163      MergeSettings(settings, section, name, [&](SettingsSpan span, Source source) {
 164          // Weird behavior preserved for backwards compatibility: Apply negated
 165          // setting even if non-negated setting would be ignored. A negated
 166          // value in the default section is applied to network specific options,
 167          // even though normal non-negated values there would be ignored.
 168          const bool never_ignore_negated_setting = span.last_negated();
 169  
 170          // Weird behavior preserved for backwards compatibility: Take first
 171          // assigned value instead of last. In general, later settings take
 172          // precedence over early settings, but for backwards compatibility in
 173          // the config file the precedence is reversed for all settings except
 174          // chain type settings.
 175          const bool reverse_precedence =
 176              (source == Source::CONFIG_FILE_NETWORK_SECTION || source == Source::CONFIG_FILE_DEFAULT_SECTION) &&
 177              !get_chain_type;
 178  
 179          // Weird behavior preserved for backwards compatibility: Negated
 180          // -regtest and -testnet arguments which you would expect to override
 181          // values set in the configuration file are currently accepted but
 182          // silently ignored. It would be better to apply these just like other
 183          // negated values, or at least warn they are ignored.
 184          const bool skip_negated_command_line = get_chain_type;
 185  
 186          if (done) return;
 187  
 188          // Ignore settings in default config section if requested.
 189          if (ignore_default_section_config && source == Source::CONFIG_FILE_DEFAULT_SECTION &&
 190              !never_ignore_negated_setting) {
 191              return;
 192          }
 193  
 194          // Ignore nonpersistent settings if requested.
 195          if (ignore_nonpersistent && (source == Source::COMMAND_LINE || source == Source::FORCED)) return;
 196  
 197          // Skip negated command line settings.
 198          if (skip_negated_command_line && span.last_negated()) return;
 199  
 200          if (!span.empty()) {
 201              result = reverse_precedence ? span.begin()[0] : span.end()[-1];
 202              done = true;
 203          } else if (span.last_negated()) {
 204              result = false;
 205              done = true;
 206          }
 207      });
 208      return result;
 209  }
 210  
 211  std::vector<SettingsValue> GetSettingsList(const Settings& settings,
 212      const std::string& section,
 213      const std::string& name,
 214      bool ignore_default_section_config)
 215  {
 216      std::vector<SettingsValue> result;
 217      bool done = false; // Done merging any more settings sources.
 218      bool prev_negated_empty = false;
 219      MergeSettings(settings, section, name, [&](SettingsSpan span, Source source) {
 220          // Weird behavior preserved for backwards compatibility: Apply config
 221          // file settings even if negated on command line. Negating a setting on
 222          // command line will ignore earlier settings on the command line and
 223          // ignore settings in the config file, unless the negated command line
 224          // value is followed by non-negated value, in which case config file
 225          // settings will be brought back from the dead (but earlier command
 226          // line settings will still be ignored).
 227          const bool add_zombie_config_values =
 228              (source == Source::CONFIG_FILE_NETWORK_SECTION || source == Source::CONFIG_FILE_DEFAULT_SECTION) &&
 229              !prev_negated_empty;
 230  
 231          // Ignore settings in default config section if requested.
 232          if (ignore_default_section_config && source == Source::CONFIG_FILE_DEFAULT_SECTION) return;
 233  
 234          // Add new settings to the result if isn't already complete, or if the
 235          // values are zombies.
 236          if (!done || add_zombie_config_values) {
 237              for (const auto& value : span) {
 238                  if (value.isArray()) {
 239                      result.insert(result.end(), value.getValues().begin(), value.getValues().end());
 240                  } else {
 241                      result.push_back(value);
 242                  }
 243              }
 244          }
 245  
 246          // If a setting was negated, or if a setting was forced, set
 247          // done to true to ignore any later lower priority settings.
 248          done |= span.negated() > 0 || source == Source::FORCED;
 249  
 250          // Update the negated and empty state used for the zombie values check.
 251          prev_negated_empty |= span.last_negated() && result.empty();
 252      });
 253      return result;
 254  }
 255  
 256  bool OnlyHasDefaultSectionSetting(const Settings& settings, const std::string& section, const std::string& name)
 257  {
 258      bool has_default_section_setting = false;
 259      bool has_other_setting = false;
 260      MergeSettings(settings, section, name, [&](SettingsSpan span, Source source) {
 261          if (span.empty()) return;
 262          else if (source == Source::CONFIG_FILE_DEFAULT_SECTION) has_default_section_setting = true;
 263          else has_other_setting = true;
 264      });
 265      // If a value is set in the default section and not explicitly overwritten by the
 266      // user on the command line or in a different section, then we want to enable
 267      // warnings about the value being ignored.
 268      return has_default_section_setting && !has_other_setting;
 269  }
 270  
 271  SettingsSpan::SettingsSpan(const std::vector<SettingsValue>& vec) noexcept : SettingsSpan(vec.data(), vec.size()) {}
 272  const SettingsValue* SettingsSpan::begin() const { return data + negated(); }
 273  const SettingsValue* SettingsSpan::end() const { return data + size; }
 274  bool SettingsSpan::empty() const { return size == 0 || last_negated(); }
 275  bool SettingsSpan::last_negated() const { return size > 0 && data[size - 1].isFalse(); }
 276  size_t SettingsSpan::negated() const
 277  {
 278      for (size_t i = size; i > 0; --i) {
 279          if (data[i - 1].isFalse()) return i; // Return number of negated values (position of last false value)
 280      }
 281      return 0;
 282  }
 283  
 284  } // namespace common
 285