|
| 1 | +// Copyright (c) 2019 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 <util/settings.h> |
| 6 | + |
| 7 | +#include <univalue.h> |
| 8 | + |
| 9 | +namespace util { |
| 10 | +namespace { |
| 11 | + |
| 12 | +enum class Source { |
| 13 | + FORCED, |
| 14 | + COMMAND_LINE, |
| 15 | + CONFIG_FILE_NETWORK_SECTION, |
| 16 | + CONFIG_FILE_DEFAULT_SECTION |
| 17 | +}; |
| 18 | + |
| 19 | +//! Merge settings from multiple sources in precedence order: |
| 20 | +//! Forced config > command line > config file network-specific section > config file default section |
| 21 | +//! |
| 22 | +//! This function is provided with a callback function fn that contains |
| 23 | +//! specific logic for how to merge the sources. |
| 24 | +template <typename Fn> |
| 25 | +static void MergeSettings(const Settings& settings, const std::string& section, const std::string& name, Fn&& fn) |
| 26 | +{ |
| 27 | + // Merge in the forced settings |
| 28 | + if (auto* value = FindKey(settings.forced_settings, name)) { |
| 29 | + fn(SettingsSpan(*value), Source::FORCED); |
| 30 | + } |
| 31 | + // Merge in the command-line options |
| 32 | + if (auto* values = FindKey(settings.command_line_options, name)) { |
| 33 | + fn(SettingsSpan(*values), Source::COMMAND_LINE); |
| 34 | + } |
| 35 | + // Merge in the network-specific section of the config file |
| 36 | + if (!section.empty()) { |
| 37 | + if (auto* map = FindKey(settings.ro_config, section)) { |
| 38 | + if (auto* values = FindKey(*map, name)) { |
| 39 | + fn(SettingsSpan(*values), Source::CONFIG_FILE_NETWORK_SECTION); |
| 40 | + } |
| 41 | + } |
| 42 | + } |
| 43 | + // Merge in the default section of the config file |
| 44 | + if (auto* map = FindKey(settings.ro_config, "")) { |
| 45 | + if (auto* values = FindKey(*map, name)) { |
| 46 | + fn(SettingsSpan(*values), Source::CONFIG_FILE_DEFAULT_SECTION); |
| 47 | + } |
| 48 | + } |
| 49 | +} |
| 50 | +} // namespace |
| 51 | + |
| 52 | +SettingsValue GetSetting(const Settings& settings, |
| 53 | + const std::string& section, |
| 54 | + const std::string& name, |
| 55 | + bool ignore_default_section_config, |
| 56 | + bool get_chain_name) |
| 57 | +{ |
| 58 | + SettingsValue result; |
| 59 | + MergeSettings(settings, section, name, [&](SettingsSpan span, Source source) { |
| 60 | + // Weird behavior preserved for backwards compatibility: Apply negated |
| 61 | + // setting even if non-negated setting would be ignored. A negated |
| 62 | + // value in the default section is applied to network specific options, |
| 63 | + // even though normal non-negated values there would be ignored. |
| 64 | + const bool never_ignore_negated_setting = span.last_negated(); |
| 65 | + |
| 66 | + // Weird behavior preserved for backwards compatibility: Take first |
| 67 | + // assigned value instead of last. In general, later settings take |
| 68 | + // precedence over early settings, but for backwards compatibility in |
| 69 | + // the config file the precedence is reversed for all settings except |
| 70 | + // chain name settings. |
| 71 | + const bool reverse_precedence = (source == Source::CONFIG_FILE_NETWORK_SECTION || source == Source::CONFIG_FILE_DEFAULT_SECTION) && !get_chain_name; |
| 72 | + |
| 73 | + // Weird behavior preserved for backwards compatibility: Negated |
| 74 | + // -regtest and -testnet arguments which you would expect to override |
| 75 | + // values set in the configuration file are currently accepted but |
| 76 | + // silently ignored. It would be better to apply these just like other |
| 77 | + // negated values, or at least warn they are ignored. |
| 78 | + const bool skip_negated_command_line = get_chain_name; |
| 79 | + |
| 80 | + // Ignore settings in default config section if requested. |
| 81 | + if (ignore_default_section_config && source == Source::CONFIG_FILE_DEFAULT_SECTION && !never_ignore_negated_setting) return; |
| 82 | + |
| 83 | + // Skip negated command line settings. |
| 84 | + if (skip_negated_command_line && span.last_negated()) return; |
| 85 | + |
| 86 | + // Stick with highest priority value, keeping result if already set. |
| 87 | + if (!result.isNull()) return; |
| 88 | + |
| 89 | + if (!span.empty()) { |
| 90 | + result = reverse_precedence ? span.begin()[0] : span.end()[-1]; |
| 91 | + } else if (span.last_negated()) { |
| 92 | + result = false; |
| 93 | + } |
| 94 | + }); |
| 95 | + return result; |
| 96 | +} |
| 97 | + |
| 98 | +std::vector<SettingsValue> GetSettingsList(const Settings& settings, |
| 99 | + const std::string& section, |
| 100 | + const std::string& name, |
| 101 | + bool ignore_default_section_config) |
| 102 | +{ |
| 103 | + std::vector<SettingsValue> result; |
| 104 | + bool result_complete = false; |
| 105 | + bool prev_negated_empty = false; |
| 106 | + MergeSettings(settings, section, name, [&](SettingsSpan span, Source source) { |
| 107 | + // Weird behavior preserved for backwards compatibility: Apply config |
| 108 | + // file settings even if negated on command line. Negating a setting on |
| 109 | + // command line will ignore earlier settings on the command line and |
| 110 | + // ignore settings in the config file, unless the negated command line |
| 111 | + // value is followed by non-negated value, in which case config file |
| 112 | + // settings will be brought back from the dead (but earlier command |
| 113 | + // line settings will still be ignored). |
| 114 | + const bool add_zombie_config_values = (source == Source::CONFIG_FILE_NETWORK_SECTION || source == Source::CONFIG_FILE_DEFAULT_SECTION) && !prev_negated_empty; |
| 115 | + |
| 116 | + // Ignore settings in default config section if requested. |
| 117 | + if (ignore_default_section_config && source == Source::CONFIG_FILE_DEFAULT_SECTION) return; |
| 118 | + |
| 119 | + // Add new settings to the result if isn't already complete, or if the |
| 120 | + // values are zombies. |
| 121 | + if (!result_complete || add_zombie_config_values) { |
| 122 | + for (const auto& value : span) { |
| 123 | + if (value.isArray()) { |
| 124 | + result.insert(result.end(), value.getValues().begin(), value.getValues().end()); |
| 125 | + } else { |
| 126 | + result.push_back(value); |
| 127 | + } |
| 128 | + } |
| 129 | + } |
| 130 | + |
| 131 | + // If a setting was negated, or if a setting was forced, set |
| 132 | + // result_complete to true to ignore any later lower priority settings. |
| 133 | + result_complete |= span.negated() > 0 || source == Source::FORCED; |
| 134 | + |
| 135 | + // Update the negated and empty state used for the zombie values check. |
| 136 | + prev_negated_empty |= span.last_negated() && result.empty(); |
| 137 | + }); |
| 138 | + return result; |
| 139 | +} |
| 140 | + |
| 141 | +bool OnlyHasDefaultSectionSetting(const Settings& settings, const std::string& section, const std::string& name) |
| 142 | +{ |
| 143 | + bool has_default_section_setting = false; |
| 144 | + bool has_other_setting = false; |
| 145 | + MergeSettings(settings, section, name, [&](SettingsSpan span, Source source) { |
| 146 | + if (span.empty()) return; |
| 147 | + else if (source == Source::CONFIG_FILE_DEFAULT_SECTION) has_default_section_setting = true; |
| 148 | + else has_other_setting = true; |
| 149 | + }); |
| 150 | + // If a value is set in the default section and not explicitly overwritten by the |
| 151 | + // user on the command line or in a different section, then we want to enable |
| 152 | + // warnings about the value being ignored. |
| 153 | + return has_default_section_setting && !has_other_setting; |
| 154 | +} |
| 155 | + |
| 156 | +SettingsSpan::SettingsSpan(const std::vector<SettingsValue>& vec) noexcept : SettingsSpan(vec.data(), vec.size()) {} |
| 157 | +const SettingsValue* SettingsSpan::begin() const { return data + negated(); } |
| 158 | +const SettingsValue* SettingsSpan::end() const { return data + size; } |
| 159 | +bool SettingsSpan::empty() const { return size == 0 || last_negated(); } |
| 160 | +bool SettingsSpan::last_negated() const { return size > 0 && data[size - 1].isFalse(); } |
| 161 | +size_t SettingsSpan::negated() const |
| 162 | +{ |
| 163 | + for (size_t i = size; i > 0; --i) { |
| 164 | + if (data[i - 1].isFalse()) return i; // Return number of negated values (position of last false value) |
| 165 | + } |
| 166 | + return 0; |
| 167 | +} |
| 168 | + |
| 169 | +} // namespace util |
0 commit comments