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