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