settings.cpp raw

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