LCOV - code coverage report
Current view: top level - src/common - settings.cpp (source / functions) Coverage Total Hit
Test: fuzz_coverage.info Lines: 85.2 % 122 104
Test Date: 2026-08-05 06:42:35 Functions: 100.0 % 16 16
Branches: 53.0 % 230 122

             Branch data     Line data    Source code
       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                 :     1723984 : static void MergeSettings(const Settings& settings, const std::string& section, const std::string& name, Fn&& fn)
      41                 :             : {
      42                 :             :     // Merge in the forced settings
      43 [ +  + ][ +  + ]:     1723984 :     if (auto* value = FindKey(settings.forced_settings, name)) {
      44                 :      106438 :         fn(SettingsSpan(*value), Source::FORCED);
      45                 :             :     }
      46                 :             :     // Merge in the command-line options
      47 [ +  + ][ +  + ]:     1723984 :     if (auto* values = FindKey(settings.command_line_options, name)) {
      48                 :       16424 :         fn(SettingsSpan(*values), Source::COMMAND_LINE);
      49                 :             :     }
      50                 :             :     // Merge in the read-write settings
      51 [ -  + ][ -  + ]:     1723984 :     if (const SettingsValue* value = FindKey(settings.rw_settings, name)) {
      52         [ +  + ]:       19134 :         fn(SettingsSpan(*value), Source::RW_SETTINGS);
      53                 :             :     }
      54                 :             :     // Merge in the network-specific section of the config file
      55 [ +  + ][ +  + ]:     1723984 :     if (!section.empty()) {
      56 [ -  + ][ -  + ]:     1627861 :         if (auto* map = FindKey(settings.ro_config, section)) {
      57 [ #  # ][ #  # ]:           0 :             if (auto* values = FindKey(*map, name)) {
      58                 :           0 :                 fn(SettingsSpan(*values), Source::CONFIG_FILE_NETWORK_SECTION);
      59                 :             :             }
      60                 :             :         }
      61                 :             :     }
      62                 :             :     // Merge in the default section of the config file
      63 [ -  + ][ -  + ]:     1723984 :     if (auto* map = FindKey(settings.ro_config, "")) {
      64 [ #  # ][ #  # ]:           0 :         if (auto* values = FindKey(*map, name)) {
      65                 :           0 :             fn(SettingsSpan(*values), Source::CONFIG_FILE_DEFAULT_SECTION);
      66                 :             :         }
      67                 :             :     }
      68                 :     1723984 : }
      69                 :             : } // namespace
      70                 :             : 
      71                 :        3589 : bool ReadSettings(const fs::path& path, std::map<std::string, SettingsValue>& values, std::vector<std::string>& errors)
      72                 :             : {
      73                 :        3589 :     values.clear();
      74                 :        3589 :     errors.clear();
      75                 :             : 
      76                 :             :     // Ok for file to not exist
      77         [ +  - ]:        3589 :     if (!fs::exists(path)) return true;
      78                 :             : 
      79                 :        3589 :     std::ifstream file;
      80         [ +  - ]:        3589 :     file.open(path.std_path());
      81         [ -  + ]:        3589 :     if (!file.is_open()) {
      82   [ #  #  #  #  :           0 :       errors.emplace_back(strprintf("%s. Please check permissions.", fs::PathToString(path)));
                   #  # ]
      83                 :           0 :       return false;
      84                 :             :     }
      85                 :             : 
      86         [ +  - ]:        3589 :     SettingsValue in;
      87   [ +  -  -  +  :        7178 :     if (!in.read(std::string{std::istreambuf_iterator<char>(file), std::istreambuf_iterator<char>()})) {
             +  -  +  + ]
      88   [ -  +  +  - ]:        1932 :         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                 :             :                                       "and can be fixed by removing the file, which will reset settings to default values.",
      90         [ +  - ]:         644 :                                       fs::PathToString(path)));
      91                 :         644 :         return false;
      92                 :             :     }
      93                 :             : 
      94         [ -  + ]:        2945 :     if (file.fail()) {
      95   [ #  #  #  #  :           0 :         errors.emplace_back(strprintf("Failed reading settings file %s", fs::PathToString(path)));
                   #  # ]
      96                 :           0 :         return false;
      97                 :             :     }
      98         [ +  - ]:        2945 :     file.close(); // Done with file descriptor. Release while copying data.
      99                 :             : 
     100         [ +  + ]:        2945 :     if (!in.isObject()) {
     101   [ -  +  +  -  :         825 :         errors.emplace_back(strprintf("Found non-object value %s in settings file %s", in.write(), fs::PathToString(path)));
             +  -  +  - ]
     102                 :         275 :         return false;
     103                 :             :     }
     104                 :             : 
     105         [ +  - ]:        2670 :     const std::vector<std::string>& in_keys = in.getKeys();
     106         [ +  - ]:        2670 :     const std::vector<SettingsValue>& in_values = in.getValues();
     107   [ -  +  +  + ]:        9715 :     for (size_t i = 0; i < in_keys.size(); ++i) {
     108         [ +  - ]:        7050 :         auto inserted = values.emplace(in_keys[i], in_values[i]);
     109         [ +  + ]:        7050 :         if (!inserted.second) {
     110   [ -  +  +  -  :          15 :             errors.emplace_back(strprintf("Found duplicate key %s in settings file %s", in_keys[i], fs::PathToString(path)));
                   +  - ]
     111                 :           5 :             values.clear();
     112                 :           5 :             break;
     113                 :             :         }
     114                 :             :     }
     115                 :             : 
     116                 :             :     // Remove auto-generated warning comment from the accessible settings.
     117                 :        2670 :     values.erase(SETTINGS_WARN_MSG_KEY);
     118                 :             : 
     119                 :        2670 :     return errors.empty();
     120                 :        3589 : }
     121                 :             : 
     122                 :       36556 : bool WriteSettings(const fs::path& path,
     123                 :             :     const std::map<std::string, SettingsValue>& values,
     124                 :             :     std::vector<std::string>& errors)
     125                 :             : {
     126                 :       36556 :     SettingsValue out(SettingsValue::VOBJ);
     127                 :             :     // Add auto-generated warning comment
     128   [ +  -  +  -  :      109668 :     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                 :             :                                                 "is running, as any changes might be ignored or overwritten.", CLIENT_NAME));
     130                 :             :     // Push settings values
     131         [ +  + ]:       73112 :     for (const auto& value : values) {
     132   [ +  -  -  +  :      109668 :         out.pushKVEnd(value.first, value.second);
                   +  - ]
     133                 :             :     }
     134         [ +  - ]:       36556 :     std::ofstream file;
     135         [ +  - ]:       36556 :     file.open(path.std_path());
     136         [ +  + ]:       36556 :     if (file.fail()) {
     137   [ -  +  +  -  :       24081 :         errors.emplace_back(strprintf("Error: Unable to open settings file %s for writing", fs::PathToString(path)));
                   +  - ]
     138                 :        8027 :         return false;
     139                 :             :     }
     140   [ +  -  +  - ]:       57058 :     file << out.write(/* prettyIndent= */ 4, /* indentLevel= */ 1) << std::endl;
     141         [ -  + ]:       28529 :     if (file.fail()) {
     142   [ #  #  #  #  :           0 :         errors.emplace_back(strprintf("Error: Unable to write settings file %s", fs::PathToString(path)));
                   #  # ]
     143                 :           0 :         return false;
     144                 :             :     }
     145         [ +  - ]:       28529 :     file.close();
     146         [ -  + ]:       28529 :     if (file.fail()) {
     147   [ #  #  #  #  :           0 :         errors.emplace_back(strprintf("Error: Unable to close settings file %s", fs::PathToString(path)));
                   #  # ]
     148                 :           0 :         return false;
     149                 :             :     }
     150                 :             :     return true;
     151                 :       36556 : }
     152                 :             : 
     153                 :     1366474 : 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                 :             : {
     160         [ +  - ]:     1366474 :     SettingsValue result;
     161                 :     1366474 :     bool done = false; // Done merging any more settings sources.
     162         [ +  - ]:     1366474 :     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                 :       95398 :         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                 :      190796 :         const bool reverse_precedence =
     175         [ -  + ]:       95398 :             (source == Source::CONFIG_FILE_NETWORK_SECTION || source == Source::CONFIG_FILE_DEFAULT_SECTION) &&
     176         [ #  # ]:           0 :             !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                 :       95398 :         const bool skip_negated_command_line = get_chain_type;
     184                 :             : 
     185         [ +  + ]:       95398 :         if (done) return;
     186                 :             : 
     187                 :             :         // Ignore settings in default config section if requested.
     188         [ +  + ]:       94595 :         if (ignore_default_section_config && source == Source::CONFIG_FILE_DEFAULT_SECTION &&
     189         [ +  - ]:        4085 :             !never_ignore_negated_setting) {
     190                 :             :             return;
     191                 :             :         }
     192                 :             : 
     193                 :             :         // Ignore nonpersistent settings if requested.
     194   [ -  +  -  - ]:       94595 :         if (ignore_nonpersistent && (source == Source::COMMAND_LINE || source == Source::FORCED)) return;
     195                 :             : 
     196                 :             :         // Skip negated command line settings.
     197   [ +  +  +  + ]:       94595 :         if (skip_negated_command_line && span.last_negated()) return;
     198                 :             : 
     199         [ +  + ]:       94594 :         if (!span.empty()) {
     200         [ -  + ]:       94176 :             result = reverse_precedence ? span.begin()[0] : span.end()[-1];
     201                 :       94176 :             done = true;
     202         [ +  - ]:         418 :         } else if (span.last_negated()) {
     203                 :         418 :             result = false;
     204                 :         418 :             done = true;
     205                 :             :         }
     206                 :             :     });
     207                 :     1366474 :     return result;
     208                 :           0 : }
     209                 :             : 
     210                 :      338376 : std::vector<SettingsValue> GetSettingsList(const Settings& settings,
     211                 :             :     const std::string& section,
     212                 :             :     const std::string& name,
     213                 :             :     bool ignore_default_section_config)
     214                 :             : {
     215                 :      338376 :     std::vector<SettingsValue> result;
     216                 :      338376 :     bool done = false; // Done merging any more settings sources.
     217                 :      338376 :     bool prev_negated_empty = false;
     218         [ +  - ]:      338376 :     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                 :       15336 :         const bool add_zombie_config_values =
     227         [ -  + ]:        7668 :             (source == Source::CONFIG_FILE_NETWORK_SECTION || source == Source::CONFIG_FILE_DEFAULT_SECTION) &&
     228         [ #  # ]:           0 :             !prev_negated_empty;
     229                 :             : 
     230                 :             :         // Ignore settings in default config section if requested.
     231   [ +  +  +  - ]:        7668 :         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   [ +  +  -  + ]:        7668 :         if (!done || add_zombie_config_values) {
     236         [ +  + ]:       15327 :             for (const auto& value : span) {
     237         [ -  + ]:        7674 :                 if (value.isArray()) {
     238                 :           0 :                     result.insert(result.end(), value.getValues().begin(), value.getValues().end());
     239                 :             :                 } else {
     240                 :        7674 :                     result.push_back(value);
     241                 :             :                 }
     242                 :             :             }
     243                 :             :         }
     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   [ +  +  +  + ]:        7668 :         done |= span.negated() > 0 || source == Source::FORCED;
     248                 :             : 
     249                 :             :         // Update the negated and empty state used for the zombie values check.
     250   [ +  +  +  + ]:        7670 :         prev_negated_empty |= span.last_negated() && result.empty();
     251                 :             :     });
     252                 :      338376 :     return result;
     253                 :           0 : }
     254                 :             : 
     255                 :       19134 : bool OnlyHasDefaultSectionSetting(const Settings& settings, const std::string& section, const std::string& name)
     256                 :             : {
     257                 :       19134 :     bool has_default_section_setting = false;
     258                 :       19134 :     bool has_other_setting = false;
     259                 :       19134 :     MergeSettings(settings, section, name, [&](SettingsSpan span, Source source) {
     260   [ +  -  +  +  :         662 :         if (span.empty()) return;
          -  -  -  -  -  
                      - ]
     261                 :           0 :         else if (source == Source::CONFIG_FILE_DEFAULT_SECTION) has_default_section_setting = true;
     262                 :         649 :         else has_other_setting = true;
     263                 :             :     });
     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   [ -  +  -  - ]:       19134 :     return has_default_section_setting && !has_other_setting;
     268                 :             : }
     269                 :             : 
     270         [ -  + ]:       18030 : SettingsSpan::SettingsSpan(const std::vector<SettingsValue>& vec) noexcept : SettingsSpan(vec.data(), vec.size()) {}
     271                 :        9173 : const SettingsValue* SettingsSpan::begin() const { return data + negated(); }
     272                 :      101829 : const SettingsValue* SettingsSpan::end() const { return data + size; }
     273   [ +  -  +  + ]:       96862 : bool SettingsSpan::empty() const { return size == 0 || last_negated(); }
     274   [ +  -  +  + ]:      200397 : bool SettingsSpan::last_negated() const { return size > 0 && data[size - 1].isFalse(); }
     275                 :       16841 : size_t SettingsSpan::negated() const
     276                 :             : {
     277         [ +  + ]:       33753 :     for (size_t i = size; i > 0; --i) {
     278         [ +  + ]:       17490 :         if (data[i - 1].isFalse()) return i; // Return number of negated values (position of last false value)
     279                 :             :     }
     280                 :             :     return 0;
     281                 :             : }
     282                 :             : 
     283                 :             : } // namespace common
        

Generated by: LCOV version 2.5.0-full