LCOV - code coverage report
Current view: top level - src/common - settings.cpp (source / functions) Hit Total Coverage
Test: fuzz_coverage.info Lines: 80 133 60.2 %
Date: 2023-11-06 23:13:05 Functions: 15 17 88.2 %
Branches: 69 218 31.7 %

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

Generated by: LCOV version 1.14