LCOV - code coverage report
Current view: top level - src/common - settings.cpp (source / functions) Hit Total Coverage
Test: fuzz_coverage.info Lines: 107 134 79.9 %
Date: 2023-09-26 12:08:55 Functions: 17 18 94.4 %

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

Generated by: LCOV version 1.14