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