Coverage Report

Created: 2026-07-14 18:13

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/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.09M
{
43
    // Merge in the forced settings
44
1.09M
    if (auto* value = FindKey(settings.forced_settings, name)) {
  Branch (44:15): [True: 243, False: 793k]
  Branch (44:15): [True: 0, False: 303k]
  Branch (44:15): [True: 0, False: 216]
45
243
        fn(SettingsSpan(*value), Source::FORCED);
46
243
    }
47
    // Merge in the command-line options
48
1.09M
    if (auto* values = FindKey(settings.command_line_options, name)) {
  Branch (48:15): [True: 783, False: 793k]
  Branch (48:15): [True: 151k, False: 151k]
  Branch (48:15): [True: 81, False: 135]
49
152k
        fn(SettingsSpan(*values), Source::COMMAND_LINE);
50
152k
    }
51
    // Merge in the read-write settings
52
1.09M
    if (const SettingsValue* value = FindKey(settings.rw_settings, name)) {
  Branch (52:30): [True: 0, False: 793k]
  Branch (52:30): [True: 0, False: 303k]
  Branch (52:30): [True: 0, False: 216]
53
0
        fn(SettingsSpan(*value), Source::RW_SETTINGS);
54
0
    }
55
    // Merge in the network-specific section of the config file
56
1.09M
    if (!section.empty()) {
  Branch (56:9): [True: 793k, False: 426]
  Branch (56:9): [True: 302k, False: 351]
  Branch (56:9): [True: 216, False: 0]
57
1.09M
        if (auto* map = FindKey(settings.ro_config, section)) {
  Branch (57:19): [True: 0, False: 793k]
  Branch (57:19): [True: 0, False: 302k]
  Branch (57:19): [True: 0, False: 216]
58
0
            if (auto* values = FindKey(*map, name)) {
  Branch (58:23): [True: 0, False: 0]
  Branch (58:23): [True: 0, False: 0]
  Branch (58:23): [True: 0, False: 0]
59
0
                fn(SettingsSpan(*values), Source::CONFIG_FILE_NETWORK_SECTION);
60
0
            }
61
0
        }
62
1.09M
    }
63
    // Merge in the default section of the config file
64
1.09M
    if (auto* map = FindKey(settings.ro_config, "")) {
  Branch (64:15): [True: 0, False: 793k]
  Branch (64:15): [True: 0, False: 303k]
  Branch (64:15): [True: 0, False: 216]
65
0
        if (auto* values = FindKey(*map, name)) {
  Branch (65:19): [True: 0, False: 0]
  Branch (65:19): [True: 0, False: 0]
  Branch (65:19): [True: 0, False: 0]
66
0
            fn(SettingsSpan(*values), Source::CONFIG_FILE_DEFAULT_SECTION);
67
0
        }
68
0
    }
69
1.09M
}
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
793k
{
43
    // Merge in the forced settings
44
793k
    if (auto* value = FindKey(settings.forced_settings, name)) {
  Branch (44:15): [True: 243, False: 793k]
45
243
        fn(SettingsSpan(*value), Source::FORCED);
46
243
    }
47
    // Merge in the command-line options
48
793k
    if (auto* values = FindKey(settings.command_line_options, name)) {
  Branch (48:15): [True: 783, False: 793k]
49
783
        fn(SettingsSpan(*values), Source::COMMAND_LINE);
50
783
    }
51
    // Merge in the read-write settings
52
793k
    if (const SettingsValue* value = FindKey(settings.rw_settings, name)) {
  Branch (52:30): [True: 0, False: 793k]
53
0
        fn(SettingsSpan(*value), Source::RW_SETTINGS);
54
0
    }
55
    // Merge in the network-specific section of the config file
56
793k
    if (!section.empty()) {
  Branch (56:9): [True: 793k, False: 426]
57
793k
        if (auto* map = FindKey(settings.ro_config, section)) {
  Branch (57:19): [True: 0, False: 793k]
58
0
            if (auto* values = FindKey(*map, name)) {
  Branch (58:23): [True: 0, False: 0]
59
0
                fn(SettingsSpan(*values), Source::CONFIG_FILE_NETWORK_SECTION);
60
0
            }
61
0
        }
62
793k
    }
63
    // Merge in the default section of the config file
64
793k
    if (auto* map = FindKey(settings.ro_config, "")) {
  Branch (64:15): [True: 0, False: 793k]
65
0
        if (auto* values = FindKey(*map, name)) {
  Branch (65:19): [True: 0, False: 0]
66
0
            fn(SettingsSpan(*values), Source::CONFIG_FILE_DEFAULT_SECTION);
67
0
        }
68
0
    }
69
793k
}
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
303k
{
43
    // Merge in the forced settings
44
303k
    if (auto* value = FindKey(settings.forced_settings, name)) {
  Branch (44:15): [True: 0, False: 303k]
45
0
        fn(SettingsSpan(*value), Source::FORCED);
46
0
    }
47
    // Merge in the command-line options
48
303k
    if (auto* values = FindKey(settings.command_line_options, name)) {
  Branch (48:15): [True: 151k, False: 151k]
49
151k
        fn(SettingsSpan(*values), Source::COMMAND_LINE);
50
151k
    }
51
    // Merge in the read-write settings
52
303k
    if (const SettingsValue* value = FindKey(settings.rw_settings, name)) {
  Branch (52:30): [True: 0, False: 303k]
53
0
        fn(SettingsSpan(*value), Source::RW_SETTINGS);
54
0
    }
55
    // Merge in the network-specific section of the config file
56
303k
    if (!section.empty()) {
  Branch (56:9): [True: 302k, False: 351]
57
302k
        if (auto* map = FindKey(settings.ro_config, section)) {
  Branch (57:19): [True: 0, False: 302k]
58
0
            if (auto* values = FindKey(*map, name)) {
  Branch (58:23): [True: 0, False: 0]
59
0
                fn(SettingsSpan(*values), Source::CONFIG_FILE_NETWORK_SECTION);
60
0
            }
61
0
        }
62
302k
    }
63
    // Merge in the default section of the config file
64
303k
    if (auto* map = FindKey(settings.ro_config, "")) {
  Branch (64:15): [True: 0, False: 303k]
65
0
        if (auto* values = FindKey(*map, name)) {
  Branch (65:19): [True: 0, False: 0]
66
0
            fn(SettingsSpan(*values), Source::CONFIG_FILE_DEFAULT_SECTION);
67
0
        }
68
0
    }
69
303k
}
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
216
{
43
    // Merge in the forced settings
44
216
    if (auto* value = FindKey(settings.forced_settings, name)) {
  Branch (44:15): [True: 0, False: 216]
45
0
        fn(SettingsSpan(*value), Source::FORCED);
46
0
    }
47
    // Merge in the command-line options
48
216
    if (auto* values = FindKey(settings.command_line_options, name)) {
  Branch (48:15): [True: 81, False: 135]
49
81
        fn(SettingsSpan(*values), Source::COMMAND_LINE);
50
81
    }
51
    // Merge in the read-write settings
52
216
    if (const SettingsValue* value = FindKey(settings.rw_settings, name)) {
  Branch (52:30): [True: 0, False: 216]
53
0
        fn(SettingsSpan(*value), Source::RW_SETTINGS);
54
0
    }
55
    // Merge in the network-specific section of the config file
56
216
    if (!section.empty()) {
  Branch (56:9): [True: 216, False: 0]
57
216
        if (auto* map = FindKey(settings.ro_config, section)) {
  Branch (57:19): [True: 0, False: 216]
58
0
            if (auto* values = FindKey(*map, name)) {
  Branch (58:23): [True: 0, False: 0]
59
0
                fn(SettingsSpan(*values), Source::CONFIG_FILE_NETWORK_SECTION);
60
0
            }
61
0
        }
62
216
    }
63
    // Merge in the default section of the config file
64
216
    if (auto* map = FindKey(settings.ro_config, "")) {
  Branch (64:15): [True: 0, False: 216]
65
0
        if (auto* values = FindKey(*map, name)) {
  Branch (65:19): [True: 0, False: 0]
66
0
            fn(SettingsSpan(*values), Source::CONFIG_FILE_DEFAULT_SECTION);
67
0
        }
68
0
    }
69
216
}
70
} // namespace
71
72
bool ReadSettings(const fs::path& path, std::map<std::string, SettingsValue>& values, std::vector<std::string>& errors)
73
27
{
74
27
    values.clear();
75
27
    errors.clear();
76
77
    // Ok for file to not exist
78
27
    if (!fs::exists(path)) return true;
  Branch (78:9): [True: 27, False: 0]
79
80
0
    std::ifstream file;
81
0
    file.open(path.std_path());
82
0
    if (!file.is_open()) {
  Branch (82:9): [True: 0, False: 0]
83
0
      errors.emplace_back(strprintf("%s. Please check permissions.", fs::PathToString(path)));
84
0
      return false;
85
0
    }
86
87
0
    SettingsValue in;
88
0
    if (!in.read(std::string{std::istreambuf_iterator<char>(file), std::istreambuf_iterator<char>()})) {
  Branch (88:9): [True: 0, False: 0]
89
0
        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
0
                                      "and can be fixed by removing the file, which will reset settings to default values.",
91
0
                                      fs::PathToString(path)));
92
0
        return false;
93
0
    }
94
95
0
    if (file.fail()) {
  Branch (95:9): [True: 0, False: 0]
96
0
        errors.emplace_back(strprintf("Failed reading settings file %s", fs::PathToString(path)));
97
0
        return false;
98
0
    }
99
0
    file.close(); // Done with file descriptor. Release while copying data.
100
101
0
    if (!in.isObject()) {
  Branch (101:9): [True: 0, False: 0]
102
0
        errors.emplace_back(strprintf("Found non-object value %s in settings file %s", in.write(), fs::PathToString(path)));
103
0
        return false;
104
0
    }
105
106
0
    const std::vector<std::string>& in_keys = in.getKeys();
107
0
    const std::vector<SettingsValue>& in_values = in.getValues();
108
0
    for (size_t i = 0; i < in_keys.size(); ++i) {
  Branch (108:24): [True: 0, False: 0]
109
0
        auto inserted = values.emplace(in_keys[i], in_values[i]);
110
0
        if (!inserted.second) {
  Branch (110:13): [True: 0, False: 0]
111
0
            errors.emplace_back(strprintf("Found duplicate key %s in settings file %s", in_keys[i], fs::PathToString(path)));
112
0
            values.clear();
113
0
            break;
114
0
        }
115
0
    }
116
117
    // Remove auto-generated warning comment from the accessible settings.
118
0
    values.erase(SETTINGS_WARN_MSG_KEY);
119
120
0
    return errors.empty();
121
0
}
122
123
bool WriteSettings(const fs::path& path,
124
    const std::map<std::string, SettingsValue>& values,
125
    std::vector<std::string>& errors)
126
54
{
127
54
    SettingsValue out(SettingsValue::VOBJ);
128
    // Add auto-generated warning comment
129
54
    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
54
                                                "is running, as any changes might be ignored or overwritten.", CLIENT_NAME));
131
    // Push settings values
132
54
    for (const auto& value : values) {
  Branch (132:28): [True: 27, False: 54]
133
27
        out.pushKVEnd(value.first, value.second);
134
27
    }
135
54
    std::ofstream file;
136
54
    file.open(path.std_path());
137
54
    if (file.fail()) {
  Branch (137:9): [True: 0, False: 54]
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
54
    file << out.write(/* prettyIndent= */ 4, /* indentLevel= */ 1) << std::endl;
142
54
    if (file.fail()) {
  Branch (142:9): [True: 0, False: 54]
143
0
        errors.emplace_back(strprintf("Error: Unable to write settings file %s", fs::PathToString(path)));
144
0
        return false;
145
0
    }
146
54
    file.close();
147
54
    if (file.fail()) {
  Branch (147:9): [True: 0, False: 54]
148
0
        errors.emplace_back(strprintf("Error: Unable to close settings file %s", fs::PathToString(path)));
149
0
        return false;
150
0
    }
151
54
    return true;
152
54
}
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
793k
{
161
793k
    SettingsValue result;
162
793k
    bool done = false; // Done merging any more settings sources.
163
793k
    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
1.02k
        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
1.02k
        const bool reverse_precedence =
176
1.02k
            (source == Source::CONFIG_FILE_NETWORK_SECTION || source == Source::CONFIG_FILE_DEFAULT_SECTION) &&
  Branch (176:14): [True: 0, False: 1.02k]
  Branch (176:63): [True: 0, False: 1.02k]
177
1.02k
            !get_chain_type;
  Branch (177:13): [True: 0, False: 0]
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
1.02k
        const bool skip_negated_command_line = get_chain_type;
185
186
1.02k
        if (done) return;
  Branch (186:13): [True: 0, False: 1.02k]
187
188
        // Ignore settings in default config section if requested.
189
1.02k
        if (ignore_default_section_config && source == Source::CONFIG_FILE_DEFAULT_SECTION &&
  Branch (189:13): [True: 108, False: 918]
  Branch (189:46): [True: 0, False: 108]
190
1.02k
            !never_ignore_negated_setting) {
  Branch (190:13): [True: 0, False: 0]
191
0
            return;
192
0
        }
193
194
        // Ignore nonpersistent settings if requested.
195
1.02k
        if (ignore_nonpersistent && (source == Source::COMMAND_LINE || source == Source::FORCED)) return;
  Branch (195:13): [True: 0, False: 1.02k]
  Branch (195:38): [True: 0, False: 0]
  Branch (195:72): [True: 0, False: 0]
196
197
        // Skip negated command line settings.
198
1.02k
        if (skip_negated_command_line && span.last_negated()) return;
  Branch (198:13): [True: 54, False: 972]
  Branch (198:42): [True: 0, False: 54]
199
200
1.02k
        if (!span.empty()) {
  Branch (200:13): [True: 972, False: 54]
201
972
            result = reverse_precedence ? span.begin()[0] : span.end()[-1];
  Branch (201:22): [True: 0, False: 972]
202
972
            done = true;
203
972
        } else if (span.last_negated()) {
  Branch (203:20): [True: 54, False: 0]
204
54
            result = false;
205
54
            done = true;
206
54
        }
207
1.02k
    });
208
793k
    return result;
209
793k
}
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
303k
{
216
303k
    std::vector<SettingsValue> result;
217
303k
    bool done = false; // Done merging any more settings sources.
218
303k
    bool prev_negated_empty = false;
219
303k
    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
151k
        const bool add_zombie_config_values =
228
151k
            (source == Source::CONFIG_FILE_NETWORK_SECTION || source == Source::CONFIG_FILE_DEFAULT_SECTION) &&
  Branch (228:14): [True: 0, False: 151k]
  Branch (228:63): [True: 0, False: 151k]
229
151k
            !prev_negated_empty;
  Branch (229:13): [True: 0, False: 0]
230
231
        // Ignore settings in default config section if requested.
232
151k
        if (ignore_default_section_config && source == Source::CONFIG_FILE_DEFAULT_SECTION) return;
  Branch (232:13): [True: 151k, False: 486]
  Branch (232:46): [True: 0, False: 151k]
233
234
        // Add new settings to the result if isn't already complete, or if the
235
        // values are zombies.
236
151k
        if (!done || add_zombie_config_values) {
  Branch (236:13): [True: 151k, False: 0]
  Branch (236:22): [True: 0, False: 0]
237
152k
            for (const auto& value : span) {
  Branch (237:36): [True: 152k, False: 151k]
238
152k
                if (value.isArray()) {
  Branch (238:21): [True: 0, False: 152k]
239
0
                    result.insert(result.end(), value.getValues().begin(), value.getValues().end());
240
152k
                } else {
241
152k
                    result.push_back(value);
242
152k
                }
243
152k
            }
244
151k
        }
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
151k
        done |= span.negated() > 0 || source == Source::FORCED;
  Branch (248:17): [True: 54, False: 151k]
  Branch (248:39): [True: 0, False: 151k]
249
250
        // Update the negated and empty state used for the zombie values check.
251
151k
        prev_negated_empty |= span.last_negated() && result.empty();
  Branch (251:31): [True: 54, False: 151k]
  Branch (251:54): [True: 54, False: 0]
252
151k
    });
253
303k
    return result;
254
303k
}
255
256
bool OnlyHasDefaultSectionSetting(const Settings& settings, const std::string& section, const std::string& name)
257
216
{
258
216
    bool has_default_section_setting = false;
259
216
    bool has_other_setting = false;
260
216
    MergeSettings(settings, section, name, [&](SettingsSpan span, Source source) {
261
81
        if (span.empty()) return;
  Branch (261:13): [True: 27, False: 54]
262
54
        else if (source == Source::CONFIG_FILE_DEFAULT_SECTION) has_default_section_setting = true;
  Branch (262:18): [True: 0, False: 54]
263
54
        else has_other_setting = true;
264
81
    });
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
216
    return has_default_section_setting && !has_other_setting;
  Branch (268:12): [True: 0, False: 216]
  Branch (268:43): [True: 0, False: 0]
269
216
}
270
271
152k
SettingsSpan::SettingsSpan(const std::vector<SettingsValue>& vec) noexcept : SettingsSpan(vec.data(), vec.size()) {}
272
151k
const SettingsValue* SettingsSpan::begin() const { return data + negated(); }
273
152k
const SettingsValue* SettingsSpan::end() const { return data + size; }
274
1.10k
bool SettingsSpan::empty() const { return size == 0 || last_negated(); }
  Branch (274:43): [True: 0, False: 1.10k]
  Branch (274:56): [True: 81, False: 1.02k]
275
153k
bool SettingsSpan::last_negated() const { return size > 0 && data[size - 1].isFalse(); }
  Branch (275:50): [True: 153k, False: 0]
  Branch (275:62): [True: 243, False: 153k]
276
size_t SettingsSpan::negated() const
277
303k
{
278
608k
    for (size_t i = size; i > 0; --i) {
  Branch (278:27): [True: 305k, False: 303k]
279
305k
        if (data[i - 1].isFalse()) return i; // Return number of negated values (position of last false value)
  Branch (279:13): [True: 108, False: 305k]
280
305k
    }
281
303k
    return 0;
282
303k
}
283
284
} // namespace common