/tmp/bitcoin/src/common/settings.cpp
Line | Count | Source |
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 | 1.11M | { |
43 | | // Merge in the forced settings |
44 | 1.11M | if (auto* value = FindKey(settings.forced_settings, name)) { |
45 | 116k | fn(SettingsSpan(*value), Source::FORCED); |
46 | 116k | } |
47 | | // Merge in the command-line options |
48 | 1.11M | if (auto* values = FindKey(settings.command_line_options, name)) { |
49 | 276k | fn(SettingsSpan(*values), Source::COMMAND_LINE); |
50 | 276k | } |
51 | | // Merge in the read-write settings |
52 | 1.11M | if (const SettingsValue* value = FindKey(settings.rw_settings, name)) { |
53 | 501 | fn(SettingsSpan(*value), Source::RW_SETTINGS); |
54 | 501 | } |
55 | | // Merge in the network-specific section of the config file |
56 | 1.11M | if (!section.empty()) { |
57 | 1.02M | if (auto* map = FindKey(settings.ro_config, section)) { |
58 | 745k | if (auto* values = FindKey(*map, name)) { |
59 | 292k | fn(SettingsSpan(*values), Source::CONFIG_FILE_NETWORK_SECTION); |
60 | 292k | } |
61 | 745k | } |
62 | 1.02M | } |
63 | | // Merge in the default section of the config file |
64 | 1.11M | if (auto* map = FindKey(settings.ro_config, "")) { |
65 | 965k | if (auto* values = FindKey(*map, name)) { |
66 | 240k | fn(SettingsSpan(*values), Source::CONFIG_FILE_DEFAULT_SECTION); |
67 | 240k | } |
68 | 965k | } |
69 | 1.11M | } settings.cpp:void common::(anonymous namespace)::MergeSettings<common::GetSetting(common::Settings const&, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char>> const&, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char>> const&, bool, bool, bool)::$_0>(common::Settings const&, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char>> const&, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char>> const&, common::GetSetting(common::Settings const&, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char>> const&, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char>> const&, bool, bool, bool)::$_0&&) Line | Count | Source | 42 | 866k | { | 43 | | // Merge in the forced settings | 44 | 866k | if (auto* value = FindKey(settings.forced_settings, name)) { | 45 | 81.3k | fn(SettingsSpan(*value), Source::FORCED); | 46 | 81.3k | } | 47 | | // Merge in the command-line options | 48 | 866k | if (auto* values = FindKey(settings.command_line_options, name)) { | 49 | 209k | fn(SettingsSpan(*values), Source::COMMAND_LINE); | 50 | 209k | } | 51 | | // Merge in the read-write settings | 52 | 866k | if (const SettingsValue* value = FindKey(settings.rw_settings, name)) { | 53 | 173 | fn(SettingsSpan(*value), Source::RW_SETTINGS); | 54 | 173 | } | 55 | | // Merge in the network-specific section of the config file | 56 | 866k | if (!section.empty()) { | 57 | 794k | if (auto* map = FindKey(settings.ro_config, section)) { | 58 | 578k | if (auto* values = FindKey(*map, name)) { | 59 | 260k | fn(SettingsSpan(*values), Source::CONFIG_FILE_NETWORK_SECTION); | 60 | 260k | } | 61 | 578k | } | 62 | 794k | } | 63 | | // Merge in the default section of the config file | 64 | 866k | if (auto* map = FindKey(settings.ro_config, "")) { | 65 | 755k | if (auto* values = FindKey(*map, name)) { | 66 | 180k | fn(SettingsSpan(*values), Source::CONFIG_FILE_DEFAULT_SECTION); | 67 | 180k | } | 68 | 755k | } | 69 | 866k | } |
settings.cpp:void common::(anonymous namespace)::MergeSettings<common::GetSettingsList(common::Settings const&, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char>> const&, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char>> const&, bool)::$_0>(common::Settings const&, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char>> const&, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char>> const&, common::GetSettingsList(common::Settings const&, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char>> const&, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char>> const&, bool)::$_0&&) Line | Count | Source | 42 | 223k | { | 43 | | // Merge in the forced settings | 44 | 223k | if (auto* value = FindKey(settings.forced_settings, name)) { | 45 | 24.8k | fn(SettingsSpan(*value), Source::FORCED); | 46 | 24.8k | } | 47 | | // Merge in the command-line options | 48 | 223k | if (auto* values = FindKey(settings.command_line_options, name)) { | 49 | 50.3k | fn(SettingsSpan(*values), Source::COMMAND_LINE); | 50 | 50.3k | } | 51 | | // Merge in the read-write settings | 52 | 223k | if (const SettingsValue* value = FindKey(settings.rw_settings, name)) { | 53 | 209 | fn(SettingsSpan(*value), Source::RW_SETTINGS); | 54 | 209 | } | 55 | | // Merge in the network-specific section of the config file | 56 | 223k | if (!section.empty()) { | 57 | 206k | if (auto* map = FindKey(settings.ro_config, section)) { | 58 | 153k | if (auto* values = FindKey(*map, name)) { | 59 | 21.5k | fn(SettingsSpan(*values), Source::CONFIG_FILE_NETWORK_SECTION); | 60 | 21.5k | } | 61 | 153k | } | 62 | 206k | } | 63 | | // Merge in the default section of the config file | 64 | 223k | if (auto* map = FindKey(settings.ro_config, "")) { | 65 | 184k | if (auto* values = FindKey(*map, name)) { | 66 | 42.6k | fn(SettingsSpan(*values), Source::CONFIG_FILE_DEFAULT_SECTION); | 67 | 42.6k | } | 68 | 184k | } | 69 | 223k | } |
settings.cpp:void common::(anonymous namespace)::MergeSettings<common::OnlyHasDefaultSectionSetting(common::Settings const&, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char>> const&, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char>> const&)::$_0>(common::Settings const&, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char>> const&, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char>> const&, common::OnlyHasDefaultSectionSetting(common::Settings const&, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char>> const&, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char>> const&)::$_0&&) Line | Count | Source | 42 | 29.4k | { | 43 | | // Merge in the forced settings | 44 | 29.4k | if (auto* value = FindKey(settings.forced_settings, name)) { | 45 | 9.92k | fn(SettingsSpan(*value), Source::FORCED); | 46 | 9.92k | } | 47 | | // Merge in the command-line options | 48 | 29.4k | if (auto* values = FindKey(settings.command_line_options, name)) { | 49 | 16.8k | fn(SettingsSpan(*values), Source::COMMAND_LINE); | 50 | 16.8k | } | 51 | | // Merge in the read-write settings | 52 | 29.4k | if (const SettingsValue* value = FindKey(settings.rw_settings, name)) { | 53 | 119 | fn(SettingsSpan(*value), Source::RW_SETTINGS); | 54 | 119 | } | 55 | | // Merge in the network-specific section of the config file | 56 | 29.4k | if (!section.empty()) { | 57 | 29.4k | if (auto* map = FindKey(settings.ro_config, section)) { | 58 | 14.4k | if (auto* values = FindKey(*map, name)) { | 59 | 9.67k | fn(SettingsSpan(*values), Source::CONFIG_FILE_NETWORK_SECTION); | 60 | 9.67k | } | 61 | 14.4k | } | 62 | 29.4k | } | 63 | | // Merge in the default section of the config file | 64 | 29.4k | if (auto* map = FindKey(settings.ro_config, "")) { | 65 | 26.1k | if (auto* values = FindKey(*map, name)) { | 66 | 16.8k | fn(SettingsSpan(*values), Source::CONFIG_FILE_DEFAULT_SECTION); | 67 | 16.8k | } | 68 | 26.1k | } | 69 | 29.4k | } |
|
70 | | } // namespace |
71 | | |
72 | | bool ReadSettings(const fs::path& path, std::map<std::string, SettingsValue>& values, std::vector<std::string>& errors) |
73 | 1.75k | { |
74 | 1.75k | values.clear(); |
75 | 1.75k | errors.clear(); |
76 | | |
77 | | // Ok for file to not exist |
78 | 1.75k | if (!fs::exists(path)) return true; |
79 | | |
80 | 1.25k | std::ifstream file; |
81 | 1.25k | file.open(path.std_path()); |
82 | 1.25k | if (!file.is_open()) { |
83 | 0 | errors.emplace_back(strprintf("%s. Please check permissions.", fs::PathToString(path))); |
84 | 0 | return false; |
85 | 0 | } |
86 | | |
87 | 1.25k | SettingsValue in; |
88 | 1.25k | if (!in.read(std::string{std::istreambuf_iterator<char>(file), std::istreambuf_iterator<char>()})) { |
89 | 2 | 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 | 2 | "and can be fixed by removing the file, which will reset settings to default values.", |
91 | 2 | fs::PathToString(path))); |
92 | 2 | return false; |
93 | 2 | } |
94 | | |
95 | 1.24k | if (file.fail()) { |
96 | 0 | errors.emplace_back(strprintf("Failed reading settings file %s", fs::PathToString(path))); |
97 | 0 | return false; |
98 | 0 | } |
99 | 1.24k | file.close(); // Done with file descriptor. Release while copying data. |
100 | | |
101 | 1.24k | if (!in.isObject()) { |
102 | 2 | errors.emplace_back(strprintf("Found non-object value %s in settings file %s", in.write(), fs::PathToString(path))); |
103 | 2 | return false; |
104 | 2 | } |
105 | | |
106 | 1.24k | const std::vector<std::string>& in_keys = in.getKeys(); |
107 | 1.24k | const std::vector<SettingsValue>& in_values = in.getValues(); |
108 | 3.18k | for (size_t i = 0; i < in_keys.size(); ++i) { |
109 | 1.94k | auto inserted = values.emplace(in_keys[i], in_values[i]); |
110 | 1.94k | if (!inserted.second) { |
111 | 2 | errors.emplace_back(strprintf("Found duplicate key %s in settings file %s", in_keys[i], fs::PathToString(path))); |
112 | 2 | values.clear(); |
113 | 2 | break; |
114 | 2 | } |
115 | 1.94k | } |
116 | | |
117 | | // Remove auto-generated warning comment from the accessible settings. |
118 | 1.24k | values.erase(SETTINGS_WARN_MSG_KEY); |
119 | | |
120 | 1.24k | return errors.empty(); |
121 | 1.24k | } |
122 | | |
123 | | bool WriteSettings(const fs::path& path, |
124 | | const std::map<std::string, SettingsValue>& values, |
125 | | std::vector<std::string>& errors) |
126 | 2.08k | { |
127 | 2.08k | SettingsValue out(SettingsValue::VOBJ); |
128 | | // Add auto-generated warning comment |
129 | 2.08k | 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 | 2.08k | "is running, as any changes might be ignored or overwritten.", CLIENT_NAME)); |
131 | | // Push settings values |
132 | 2.08k | for (const auto& value : values) { |
133 | 1.03k | out.pushKVEnd(value.first, value.second); |
134 | 1.03k | } |
135 | 2.08k | std::ofstream file; |
136 | 2.08k | file.open(path.std_path()); |
137 | 2.08k | if (file.fail()) { |
138 | 0 | errors.emplace_back(strprintf("Error: Unable to open settings file %s for writing", fs::PathToString(path))); |
139 | 0 | return false; |
140 | 0 | } |
141 | 2.08k | file << out.write(/* prettyIndent= */ 4, /* indentLevel= */ 1) << std::endl; |
142 | 2.08k | if (file.fail()) { |
143 | 0 | errors.emplace_back(strprintf("Error: Unable to write settings file %s", fs::PathToString(path))); |
144 | 0 | return false; |
145 | 0 | } |
146 | 2.08k | file.close(); |
147 | 2.08k | if (file.fail()) { |
148 | 0 | errors.emplace_back(strprintf("Error: Unable to close settings file %s", fs::PathToString(path))); |
149 | 0 | return false; |
150 | 0 | } |
151 | 2.08k | return true; |
152 | 2.08k | } |
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 | 866k | { |
161 | 866k | SettingsValue result; |
162 | 866k | bool done = false; // Done merging any more settings sources. |
163 | 866k | 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 | 732k | 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 | 732k | const bool reverse_precedence = |
176 | 732k | (source == Source::CONFIG_FILE_NETWORK_SECTION || source == Source::CONFIG_FILE_DEFAULT_SECTION) && |
177 | 732k | !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 | 732k | const bool skip_negated_command_line = get_chain_type; |
185 | | |
186 | 732k | if (done) return; |
187 | | |
188 | | // Ignore settings in default config section if requested. |
189 | 473k | if (ignore_default_section_config && source == Source::CONFIG_FILE_DEFAULT_SECTION && |
190 | 473k | !never_ignore_negated_setting) { |
191 | 1.08k | return; |
192 | 1.08k | } |
193 | | |
194 | | // Ignore nonpersistent settings if requested. |
195 | 472k | if (ignore_nonpersistent && (source == Source::COMMAND_LINE || source == Source::FORCED)) return; |
196 | | |
197 | | // Skip negated command line settings. |
198 | 472k | if (skip_negated_command_line && span.last_negated()) return; |
199 | | |
200 | 471k | if (!span.empty()) { |
201 | 410k | result = reverse_precedence ? span.begin()[0] : span.end()[-1]; |
202 | 410k | done = true; |
203 | 410k | } else if (span.last_negated()) { |
204 | 60.6k | result = false; |
205 | 60.6k | done = true; |
206 | 60.6k | } |
207 | 471k | }); |
208 | 866k | return result; |
209 | 866k | } |
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 | 223k | { |
216 | 223k | std::vector<SettingsValue> result; |
217 | 223k | bool done = false; // Done merging any more settings sources. |
218 | 223k | bool prev_negated_empty = false; |
219 | 223k | 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 | 139k | const bool add_zombie_config_values = |
228 | 139k | (source == Source::CONFIG_FILE_NETWORK_SECTION || source == Source::CONFIG_FILE_DEFAULT_SECTION) && |
229 | 139k | !prev_negated_empty; |
230 | | |
231 | | // Ignore settings in default config section if requested. |
232 | 139k | 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 | 123k | if (!done || add_zombie_config_values) { |
237 | 98.5k | for (const auto& value : span) { |
238 | 98.5k | if (value.isArray()) { |
239 | 173 | result.insert(result.end(), value.getValues().begin(), value.getValues().end()); |
240 | 98.3k | } else { |
241 | 98.3k | result.push_back(value); |
242 | 98.3k | } |
243 | 98.5k | } |
244 | 94.0k | } |
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 | 123k | done |= span.negated() > 0 || source == Source::FORCED; |
249 | | |
250 | | // Update the negated and empty state used for the zombie values check. |
251 | 123k | prev_negated_empty |= span.last_negated() && result.empty(); |
252 | 123k | }); |
253 | 223k | return result; |
254 | 223k | } |
255 | | |
256 | | bool OnlyHasDefaultSectionSetting(const Settings& settings, const std::string& section, const std::string& name) |
257 | 29.4k | { |
258 | 29.4k | bool has_default_section_setting = false; |
259 | 29.4k | bool has_other_setting = false; |
260 | 53.4k | MergeSettings(settings, section, name, [&](SettingsSpan span, Source source) { |
261 | 53.4k | if (span.empty()) return; |
262 | 33.8k | else if (source == Source::CONFIG_FILE_DEFAULT_SECTION) has_default_section_setting = true; |
263 | 25.5k | else has_other_setting = true; |
264 | 53.4k | }); |
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 | 29.4k | return has_default_section_setting && !has_other_setting; |
269 | 29.4k | } |
270 | | |
271 | 809k | SettingsSpan::SettingsSpan(const std::vector<SettingsValue>& vec) noexcept : SettingsSpan(vec.data(), vec.size()) {} |
272 | 320k | const SettingsValue* SettingsSpan::begin() const { return data + negated(); } |
273 | 278k | const SettingsValue* SettingsSpan::end() const { return data + size; } |
274 | 525k | bool SettingsSpan::empty() const { return size == 0 || last_negated(); } |
275 | 1.45M | bool SettingsSpan::last_negated() const { return size > 0 && data[size - 1].isFalse(); } |
276 | | size_t SettingsSpan::negated() const |
277 | 444k | { |
278 | 904k | for (size_t i = size; i > 0; --i) { |
279 | 560k | if (data[i - 1].isFalse()) return i; // Return number of negated values (position of last false value) |
280 | 560k | } |
281 | 343k | return 0; |
282 | 444k | } |
283 | | |
284 | | } // namespace common |