Coverage Report

Created: 2026-08-14 20:23

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/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