LCOV - code coverage report
Current view: top level - src/common - settings.cpp (source / functions) Coverage Total Hit
Test: test_bitcoin_coverage.info Lines: 92.2 % 116 107
Test Date: 2024-11-04 04:45:35 Functions: 100.0 % 16 16
Branches: 68.6 % 194 133

             Branch data     Line data    Source code
       1                 :             : // Copyright (c) 2019-2022 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                 :      332703 : static void MergeSettings(const Settings& settings, const std::string& section, const std::string& name, Fn&& fn)
      42                 :             : {
      43                 :             :     // Merge in the forced settings
      44         [ +  + ]:      332703 :     if (auto* value = FindKey(settings.forced_settings, name)) {
      45                 :      131964 :         fn(SettingsSpan(*value), Source::FORCED);
      46                 :             :     }
      47                 :             :     // Merge in the command-line options
      48         [ +  + ]:      332703 :     if (auto* values = FindKey(settings.command_line_options, name)) {
      49                 :      231003 :         fn(SettingsSpan(*values), Source::COMMAND_LINE);
      50                 :             :     }
      51                 :             :     // Merge in the read-write settings
      52         [ +  + ]:      332703 :     if (const SettingsValue* value = FindKey(settings.rw_settings, name)) {
      53         [ +  - ]:       20045 :         fn(SettingsSpan(*value), Source::RW_SETTINGS);
      54                 :             :     }
      55                 :             :     // Merge in the network-specific section of the config file
      56         [ +  + ]:      332703 :     if (!section.empty()) {
      57         [ +  + ]:      319078 :         if (auto* map = FindKey(settings.ro_config, section)) {
      58         [ +  + ]:       59285 :             if (auto* values = FindKey(*map, name)) {
      59                 :       59264 :                 fn(SettingsSpan(*values), Source::CONFIG_FILE_NETWORK_SECTION);
      60                 :             :             }
      61                 :             :         }
      62                 :             :     }
      63                 :             :     // Merge in the default section of the config file
      64         [ +  + ]:      332703 :     if (auto* map = FindKey(settings.ro_config, "")) {
      65         [ +  + ]:      232652 :         if (auto* values = FindKey(*map, name)) {
      66                 :      227932 :             fn(SettingsSpan(*values), Source::CONFIG_FILE_DEFAULT_SECTION);
      67                 :             :         }
      68                 :             :     }
      69                 :      332703 : }
      70                 :             : } // namespace
      71                 :             : 
      72                 :           9 : bool ReadSettings(const fs::path& path, std::map<std::string, SettingsValue>& values, std::vector<std::string>& errors)
      73                 :             : {
      74                 :           9 :     values.clear();
      75                 :           9 :     errors.clear();
      76                 :             : 
      77                 :             :     // Ok for file to not exist
      78         [ +  + ]:           9 :     if (!fs::exists(path)) return true;
      79                 :             : 
      80                 :           8 :     std::ifstream file;
      81         [ +  - ]:           8 :     file.open(path);
      82         [ -  + ]:           8 :     if (!file.is_open()) {
      83   [ #  #  #  #  :           0 :       errors.emplace_back(strprintf("%s. Please check permissions.", fs::PathToString(path)));
                   #  # ]
      84                 :           0 :       return false;
      85                 :             :     }
      86                 :             : 
      87         [ +  - ]:           8 :     SettingsValue in;
      88   [ +  -  +  -  :          16 :     if (!in.read(std::string{std::istreambuf_iterator<char>(file), std::istreambuf_iterator<char>()})) {
                   +  + ]
      89   [ +  -  +  - ]:           3 :         errors.emplace_back(strprintf("Settings file %s does not contain valid JSON. This is probably caused by disk corruption or a crash, "
      90                 :             :                                       "and can be fixed by removing the file, which will reset settings to default values.",
      91         [ +  - ]:           1 :                                       fs::PathToString(path)));
      92                 :           1 :         return false;
      93                 :             :     }
      94                 :             : 
      95         [ -  + ]:           7 :     if (file.fail()) {
      96   [ #  #  #  #  :           0 :         errors.emplace_back(strprintf("Failed reading settings file %s", fs::PathToString(path)));
                   #  # ]
      97                 :           0 :         return false;
      98                 :             :     }
      99         [ +  - ]:           7 :     file.close(); // Done with file descriptor. Release while copying data.
     100                 :             : 
     101         [ +  + ]:           7 :     if (!in.isObject()) {
     102   [ +  -  +  -  :           3 :         errors.emplace_back(strprintf("Found non-object value %s in settings file %s", in.write(), fs::PathToString(path)));
             +  -  +  - ]
     103                 :           1 :         return false;
     104                 :             :     }
     105                 :             : 
     106         [ +  - ]:           6 :     const std::vector<std::string>& in_keys = in.getKeys();
     107         [ +  - ]:           6 :     const std::vector<SettingsValue>& in_values = in.getValues();
     108         [ +  + ]:          18 :     for (size_t i = 0; i < in_keys.size(); ++i) {
     109         [ +  - ]:          13 :         auto inserted = values.emplace(in_keys[i], in_values[i]);
     110         [ +  + ]:          13 :         if (!inserted.second) {
     111   [ +  -  +  -  :           3 :             errors.emplace_back(strprintf("Found duplicate key %s in settings file %s", in_keys[i], fs::PathToString(path)));
                   +  - ]
     112                 :           1 :             values.clear();
     113                 :           1 :             break;
     114                 :             :         }
     115                 :             :     }
     116                 :             : 
     117                 :             :     // Remove auto-generated warning comment from the accessible settings.
     118                 :           6 :     values.erase(SETTINGS_WARN_MSG_KEY);
     119                 :             : 
     120                 :           6 :     return errors.empty();
     121                 :           8 : }
     122                 :             : 
     123                 :         193 : bool WriteSettings(const fs::path& path,
     124                 :             :     const std::map<std::string, SettingsValue>& values,
     125                 :             :     std::vector<std::string>& errors)
     126                 :             : {
     127                 :         193 :     SettingsValue out(SettingsValue::VOBJ);
     128                 :             :     // Add auto-generated warning comment
     129   [ +  -  +  -  :         386 :     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                 :             :                                                 "is running, as any changes might be ignored or overwritten.", CLIENT_NAME));
     131                 :             :     // Push settings values
     132         [ +  + ]:         386 :     for (const auto& value : values) {
     133   [ +  -  +  -  :         386 :         out.pushKVEnd(value.first, value.second);
                   +  - ]
     134                 :             :     }
     135         [ +  - ]:         193 :     std::ofstream file;
     136         [ +  - ]:         193 :     file.open(path);
     137         [ -  + ]:         193 :     if (file.fail()) {
     138   [ #  #  #  #  :           0 :         errors.emplace_back(strprintf("Error: Unable to open settings file %s for writing", fs::PathToString(path)));
                   #  # ]
     139                 :           0 :         return false;
     140                 :             :     }
     141   [ +  -  +  - ]:         386 :     file << out.write(/* prettyIndent= */ 4, /* indentLevel= */ 1) << std::endl;
     142         [ +  - ]:         193 :     file.close();
     143                 :             :     return true;
     144                 :         193 : }
     145                 :             : 
     146                 :      258899 : SettingsValue GetSetting(const Settings& settings,
     147                 :             :     const std::string& section,
     148                 :             :     const std::string& name,
     149                 :             :     bool ignore_default_section_config,
     150                 :             :     bool ignore_nonpersistent,
     151                 :             :     bool get_chain_type)
     152                 :             : {
     153         [ +  - ]:      258899 :     SettingsValue result;
     154                 :      258899 :     bool done = false; // Done merging any more settings sources.
     155         [ +  - ]:      258899 :     MergeSettings(settings, section, name, [&](SettingsSpan span, Source source) {
     156                 :             :         // Weird behavior preserved for backwards compatibility: Apply negated
     157                 :             :         // setting even if non-negated setting would be ignored. A negated
     158                 :             :         // value in the default section is applied to network specific options,
     159                 :             :         // even though normal non-negated values there would be ignored.
     160                 :      458355 :         const bool never_ignore_negated_setting = span.last_negated();
     161                 :             : 
     162                 :             :         // Weird behavior preserved for backwards compatibility: Take first
     163                 :             :         // assigned value instead of last. In general, later settings take
     164                 :             :         // precedence over early settings, but for backwards compatibility in
     165                 :             :         // the config file the precedence is reversed for all settings except
     166                 :             :         // chain type settings.
     167                 :      916710 :         const bool reverse_precedence =
     168         [ +  + ]:      458355 :             (source == Source::CONFIG_FILE_NETWORK_SECTION || source == Source::CONFIG_FILE_DEFAULT_SECTION) &&
     169         [ +  + ]:      210894 :             !get_chain_type;
     170                 :             : 
     171                 :             :         // Weird behavior preserved for backwards compatibility: Negated
     172                 :             :         // -regtest and -testnet arguments which you would expect to override
     173                 :             :         // values set in the configuration file are currently accepted but
     174                 :             :         // silently ignored. It would be better to apply these just like other
     175                 :             :         // negated values, or at least warn they are ignored.
     176                 :      458355 :         const bool skip_negated_command_line = get_chain_type;
     177                 :             : 
     178         [ +  + ]:      458355 :         if (done) return;
     179                 :             : 
     180                 :             :         // Ignore settings in default config section if requested.
     181         [ +  + ]:      200991 :         if (ignore_default_section_config && source == Source::CONFIG_FILE_DEFAULT_SECTION &&
     182         [ +  + ]:       71724 :             !never_ignore_negated_setting) {
     183                 :             :             return;
     184                 :             :         }
     185                 :             : 
     186                 :             :         // Ignore nonpersistent settings if requested.
     187   [ -  +  -  - ]:      199906 :         if (ignore_nonpersistent && (source == Source::COMMAND_LINE || source == Source::FORCED)) return;
     188                 :             : 
     189                 :             :         // Skip negated command line settings.
     190   [ +  +  +  + ]:      199906 :         if (skip_negated_command_line && span.last_negated()) return;
     191                 :             : 
     192         [ +  + ]:      198896 :         if (!span.empty()) {
     193         [ +  + ]:      139875 :             result = reverse_precedence ? span.begin()[0] : span.end()[-1];
     194                 :      139875 :             done = true;
     195         [ +  + ]:       59021 :         } else if (span.last_negated()) {
     196                 :       58914 :             result = false;
     197                 :       58914 :             done = true;
     198                 :             :         }
     199                 :             :     });
     200                 :      258899 :     return result;
     201                 :           0 : }
     202                 :             : 
     203                 :       53824 : std::vector<SettingsValue> GetSettingsList(const Settings& settings,
     204                 :             :     const std::string& section,
     205                 :             :     const std::string& name,
     206                 :             :     bool ignore_default_section_config)
     207                 :             : {
     208                 :       53824 :     std::vector<SettingsValue> result;
     209                 :       53824 :     bool done = false; // Done merging any more settings sources.
     210                 :       53824 :     bool prev_negated_empty = false;
     211         [ +  - ]:       53824 :     MergeSettings(settings, section, name, [&](SettingsSpan span, Source source) {
     212                 :             :         // Weird behavior preserved for backwards compatibility: Apply config
     213                 :             :         // file settings even if negated on command line. Negating a setting on
     214                 :             :         // command line will ignore earlier settings on the command line and
     215                 :             :         // ignore settings in the config file, unless the negated command line
     216                 :             :         // value is followed by non-negated value, in which case config file
     217                 :             :         // settings will be brought back from the dead (but earlier command
     218                 :             :         // line settings will still be ignored).
     219                 :      246318 :         const bool add_zombie_config_values =
     220         [ +  + ]:      123159 :             (source == Source::CONFIG_FILE_NETWORK_SECTION || source == Source::CONFIG_FILE_DEFAULT_SECTION) &&
     221         [ +  + ]:       54266 :             !prev_negated_empty;
     222                 :             : 
     223                 :             :         // Ignore settings in default config section if requested.
     224   [ +  +  +  + ]:      123159 :         if (ignore_default_section_config && source == Source::CONFIG_FILE_DEFAULT_SECTION) return;
     225                 :             : 
     226                 :             :         // Add new settings to the result if isn't already complete, or if the
     227                 :             :         // values are zombies.
     228   [ +  +  +  + ]:      106985 :         if (!done || add_zombie_config_values) {
     229         [ +  + ]:      158023 :             for (const auto& value : span) {
     230         [ -  + ]:       80512 :                 if (value.isArray()) {
     231                 :           0 :                     result.insert(result.end(), value.getValues().begin(), value.getValues().end());
     232                 :             :                 } else {
     233                 :       80512 :                     result.push_back(value);
     234                 :             :                 }
     235                 :             :             }
     236                 :             :         }
     237                 :             : 
     238                 :             :         // If a setting was negated, or if a setting was forced, set
     239                 :             :         // done to true to ignore any later lower priority settings.
     240   [ +  +  +  + ]:      106985 :         done |= span.negated() > 0 || source == Source::FORCED;
     241                 :             : 
     242                 :             :         // Update the negated and empty state used for the zombie values check.
     243   [ +  +  +  + ]:      131161 :         prev_negated_empty |= span.last_negated() && result.empty();
     244                 :             :     });
     245                 :       53824 :     return result;
     246                 :           0 : }
     247                 :             : 
     248                 :       19980 : bool OnlyHasDefaultSectionSetting(const Settings& settings, const std::string& section, const std::string& name)
     249                 :             : {
     250                 :       19980 :     bool has_default_section_setting = false;
     251                 :       19980 :     bool has_other_setting = false;
     252                 :       19980 :     MergeSettings(settings, section, name, [&](SettingsSpan span, Source source) {
     253   [ +  -  +  +  :       48741 :         if (span.empty()) return;
          +  -  +  +  +  
                      + ]
     254                 :        8372 :         else if (source == Source::CONFIG_FILE_DEFAULT_SECTION) has_default_section_setting = true;
     255                 :       20801 :         else has_other_setting = true;
     256                 :             :     });
     257                 :             :     // If a value is set in the default section and not explicitly overwritten by the
     258                 :             :     // user on the command line or in a different section, then we want to enable
     259                 :             :     // warnings about the value being ignored.
     260   [ +  +  +  + ]:       19980 :     return has_default_section_setting && !has_other_setting;
     261                 :             : }
     262                 :             : 
     263                 :      518202 : SettingsSpan::SettingsSpan(const std::vector<SettingsValue>& vec) noexcept : SettingsSpan(vec.data(), vec.size()) {}
     264                 :       83053 : const SettingsValue* SettingsSpan::begin() const { return data + negated(); }
     265                 :      211846 : const SettingsValue* SettingsSpan::end() const { return data + size; }
     266   [ +  +  +  + ]:      247640 : bool SettingsSpan::empty() const { return size == 0 || last_negated(); }
     267   [ +  +  +  + ]:      874594 : bool SettingsSpan::last_negated() const { return size > 0 && data[size - 1].isFalse(); }
     268                 :      190038 : size_t SettingsSpan::negated() const
     269                 :             : {
     270         [ +  + ]:      393110 :     for (size_t i = size; i > 0; --i) {
     271         [ +  + ]:      303119 :         if (data[i - 1].isFalse()) return i; // Return number of negated values (position of last false value)
     272                 :             :     }
     273                 :             :     return 0;
     274                 :             : }
     275                 :             : 
     276                 :             : } // namespace common
        

Generated by: LCOV version 2.0-1