From b3a5b16a95b594986f46f36edafaf1fc2fda80b9 Mon Sep 17 00:00:00 2001 From: Luke Dashjr Date: Fri, 10 Apr 2020 23:25:33 +0000 Subject: [PATCH 1/6] util/settings: Add place to put rwconf settings --- src/common/args.cpp | 1 + src/common/config.cpp | 1 + src/common/settings.cpp | 9 +++++++-- src/common/settings.h | 2 ++ 4 files changed, 11 insertions(+), 2 deletions(-) diff --git a/src/common/args.cpp b/src/common/args.cpp index a9108e5916..6e69a4b1c5 100644 --- a/src/common/args.cpp +++ b/src/common/args.cpp @@ -815,6 +815,7 @@ void ArgsManager::LogArgs() const for (const auto& setting : m_settings.rw_settings) { LogPrintf("Setting file arg: %s = %s\n", setting.first, setting.second.write()); } + logArgsPrefix("R/W config file arg:", "", m_settings.rw_config); logArgsPrefix("Command-line arg:", "", m_settings.command_line_options); } diff --git a/src/common/config.cpp b/src/common/config.cpp index 1c85273f69..241d5b7a07 100644 --- a/src/common/config.cpp +++ b/src/common/config.cpp @@ -120,6 +120,7 @@ bool ArgsManager::ReadConfigFiles(std::string& error, bool ignore_invalid_keys) { LOCK(cs_args); m_settings.ro_config.clear(); + m_settings.rw_config.clear(); m_config_sections.clear(); m_config_path = AbsPathForConfigVal(*this, GetPathArg("-conf", BITCOIN_CONF_FILENAME), /*net_specific=*/false); } diff --git a/src/common/settings.cpp b/src/common/settings.cpp index db1001111a..05ef03094a 100644 --- a/src/common/settings.cpp +++ b/src/common/settings.cpp @@ -26,6 +26,7 @@ namespace { enum class Source { FORCED, COMMAND_LINE, + CONFIG_FILE_RW, RW_SETTINGS, CONFIG_FILE_NETWORK_SECTION, CONFIG_FILE_DEFAULT_SECTION @@ -50,6 +51,10 @@ static void MergeSettings(const Settings& settings, const std::string& section, if (auto* values = FindKey(settings.command_line_options, name)) { fn(SettingsSpan(*values), Source::COMMAND_LINE); } + // Merge in the rw config file + if (auto* values = FindKey(settings.rw_config, name)) { + fn(SettingsSpan(*values), Source::CONFIG_FILE_RW); + } // Merge in the read-write settings if (const SettingsValue* value = FindKey(settings.rw_settings, name)) { fn(SettingsSpan(*value), Source::RW_SETTINGS); @@ -167,7 +172,7 @@ SettingsValue GetSetting(const Settings& settings, // the config file the precedence is reversed for all settings except // chain type settings. const bool reverse_precedence = - (source == Source::CONFIG_FILE_NETWORK_SECTION || source == Source::CONFIG_FILE_DEFAULT_SECTION) && + (source == Source::CONFIG_FILE_RW || source == Source::CONFIG_FILE_NETWORK_SECTION || source == Source::CONFIG_FILE_DEFAULT_SECTION) && !get_chain_type; // Weird behavior preserved for backwards compatibility: Negated @@ -219,7 +224,7 @@ std::vector GetSettingsList(const Settings& settings, // settings will be brought back from the dead (but earlier command // line settings will still be ignored). const bool add_zombie_config_values = - (source == Source::CONFIG_FILE_NETWORK_SECTION || source == Source::CONFIG_FILE_DEFAULT_SECTION) && + (source == Source::CONFIG_FILE_RW || source == Source::CONFIG_FILE_NETWORK_SECTION || source == Source::CONFIG_FILE_DEFAULT_SECTION) && !prev_negated_empty; // Ignore settings in default config section if requested. diff --git a/src/common/settings.h b/src/common/settings.h index 0e9d376e23..e00f580314 100644 --- a/src/common/settings.h +++ b/src/common/settings.h @@ -34,6 +34,8 @@ struct Settings { std::map forced_settings; //! Map of setting name to list of command line values. std::map> command_line_options; + //! Map of setting name to list of r/w config file values. + std::map> rw_config; //! Map of setting name to read-write file setting value. std::map rw_settings; //! Map of config section name and setting name to list of config file values. From 0c47ea455c5990ee3a03f97ae57519197a56d8a5 Mon Sep 17 00:00:00 2001 From: Luke Dashjr Date: Wed, 7 Nov 2018 07:38:01 +0000 Subject: [PATCH 2/6] util: SelectBaseParams in ReadConfigFiles, before getting final datadir --- src/bitcoin-cli.cpp | 7 ------- src/common/config.cpp | 9 +++++++++ 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/src/bitcoin-cli.cpp b/src/bitcoin-cli.cpp index f0e27cb675..16efd5b440 100644 --- a/src/bitcoin-cli.cpp +++ b/src/bitcoin-cli.cpp @@ -173,13 +173,6 @@ static int AppInitRPC(int argc, char* argv[]) tfm::format(std::cerr, "Error reading configuration file: %s\n", error); return EXIT_FAILURE; } - // Check for chain settings (BaseParams() calls are only valid after this clause) - try { - SelectBaseParams(gArgs.GetChainType()); - } catch (const std::exception& e) { - tfm::format(std::cerr, "Error: %s\n", e.what()); - return EXIT_FAILURE; - } return CONTINUE_EXECUTION; } diff --git a/src/common/config.cpp b/src/common/config.cpp index 241d5b7a07..d32b915009 100644 --- a/src/common/config.cpp +++ b/src/common/config.cpp @@ -4,6 +4,7 @@ #include +#include #include #include #include @@ -200,6 +201,14 @@ bool ArgsManager::ReadConfigFiles(std::string& error, bool ignore_invalid_keys) } } + // Check for chain settings (BaseParams() calls are only valid after this clause) + try { + SelectBaseParams(gArgs.GetChainType()); + } catch (const std::exception& e) { + error = e.what(); + return false; + } + // If datadir is changed in .conf file: ClearPathCache(); if (!CheckDataDirOption(*this)) { From 7a9905d9cc987584885159d127bb7289b6b02569 Mon Sep 17 00:00:00 2001 From: Luke Dashjr Date: Sat, 11 Apr 2020 00:51:23 +0000 Subject: [PATCH 3/6] util/settings: Support ArgsManager::ReadConfigStream into other targets --- src/common/args.h | 2 +- src/common/config.cpp | 5 ++++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/src/common/args.h b/src/common/args.h index 6451b194d1..0a94a74d50 100644 --- a/src/common/args.h +++ b/src/common/args.h @@ -142,7 +142,7 @@ protected: mutable fs::path m_cached_datadir_path GUARDED_BY(cs_args); mutable fs::path m_cached_network_datadir_path GUARDED_BY(cs_args); - [[nodiscard]] bool ReadConfigStream(std::istream& stream, const std::string& filepath, std::string& error, bool ignore_invalid_keys = false); + [[nodiscard]] bool ReadConfigStream(std::istream& stream, const std::string& filepath, std::string& error, bool ignore_invalid_keys = false, std::map>* settings_target = nullptr); /** * Returns true if settings values from the default section should be used, diff --git a/src/common/config.cpp b/src/common/config.cpp index d32b915009..4b0f45f868 100644 --- a/src/common/config.cpp +++ b/src/common/config.cpp @@ -87,7 +87,7 @@ bool IsConfSupported(KeyInfo& key, std::string& error) { return true; } -bool ArgsManager::ReadConfigStream(std::istream& stream, const std::string& filepath, std::string& error, bool ignore_invalid_keys) +bool ArgsManager::ReadConfigStream(std::istream& stream, const std::string& filepath, std::string& error, bool ignore_invalid_keys, std::map>* settings_target) { LOCK(cs_args); std::vector> options; @@ -103,6 +103,9 @@ bool ArgsManager::ReadConfigStream(std::istream& stream, const std::string& file if (!value) { return false; } + if (settings_target) { + (*settings_target)[key.name].push_back(*value); + } else m_settings.ro_config[key.section][key.name].push_back(*value); } else { if (ignore_invalid_keys) { From 5a311113cac9b13518b6de905c81dda3515e8496 Mon Sep 17 00:00:00 2001 From: Luke Dashjr Date: Thu, 11 Feb 2016 04:28:59 +0000 Subject: [PATCH 4/6] Add new bitcoin_rw.conf file that is used for settings modified by this software itself --- doc/files.md | 1 + src/bitcoin-cli.cpp | 1 + src/common/args.cpp | 220 +++++++++++++++++++++++++++++++++++++++ src/common/args.h | 9 ++ src/common/config.cpp | 11 ++ src/init.cpp | 1 + src/init/common.cpp | 9 ++ src/qt/optionsmodel.cpp | 3 + src/qt/test/apptests.cpp | 9 ++ src/test/util_tests.cpp | 153 +++++++++++++++++++++++++++ 10 files changed, 417 insertions(+) diff --git a/doc/files.md b/doc/files.md index f88d3f91a1..d94007771b 100644 --- a/doc/files.md +++ b/doc/files.md @@ -58,6 +58,7 @@ Subdirectory | File(s) | Description `./` | `anchors.dat` | Anchor IP address database, created on shutdown and deleted at startup. Anchors are last known outgoing block-relay-only peers that are tried to re-connect to on startup `./` | `banlist.json` | Stores the addresses/subnets of banned nodes. `./` | `bitcoin.conf` | User-defined [configuration settings](bitcoin-conf.md) for `bitcoind` or `bitcoin-qt`. File is not written to by the software and must be created manually. Path can be specified by `-conf` option +`./` | `bitcoin_rw.conf` | Contains [configuration settings](bitcoin-conf.md) modified by `bitcoind` or `bitcoin-qt`; can be specified by `-confrw` option `./` | `bitcoind.pid` | Stores the process ID (PID) of `bitcoind` or `bitcoin-qt` while running; created at start and deleted on shutdown; can be specified by `-pid` option `./` | `debug.log` | Contains debug information and general logging generated by `bitcoind` or `bitcoin-qt`; can be specified by `-debuglogfile` option `./` | `fee_estimates.dat` | Stores statistics used to estimate minimum transaction fees required for confirmation diff --git a/src/bitcoin-cli.cpp b/src/bitcoin-cli.cpp index 16efd5b440..ab144bf0d7 100644 --- a/src/bitcoin-cli.cpp +++ b/src/bitcoin-cli.cpp @@ -81,6 +81,7 @@ static void SetupCliArgs(ArgsManager& argsman) argsman.AddArg("-version", "Print version and exit", ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS); argsman.AddArg("-conf=", strprintf("Specify configuration file. Relative paths will be prefixed by datadir location. (default: %s)", BITCOIN_CONF_FILENAME), ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS); + argsman.AddArg("-confrw=", strprintf("Specify read/write configuration file. Relative paths will be prefixed by the network-specific datadir location. (default: %s)", BITCOIN_RW_CONF_FILENAME), ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS); argsman.AddArg("-datadir=", "Specify data directory", ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS); argsman.AddArg("-generate", strprintf("Generate blocks, equivalent to RPC getnewaddress followed by RPC generatetoaddress. Optional positional integer " diff --git a/src/common/args.cpp b/src/common/args.cpp index 6e69a4b1c5..94e7457f8b 100644 --- a/src/common/args.cpp +++ b/src/common/args.cpp @@ -28,15 +28,18 @@ #include #include #include +#include #include #include #include #include +#include #include #include const char * const BITCOIN_CONF_FILENAME = "bitcoin.conf"; const char * const BITCOIN_SETTINGS_FILENAME = "settings.json"; +const char * const BITCOIN_RW_CONF_FILENAME = "bitcoin_rw.conf"; ArgsManager gArgs; @@ -726,6 +729,12 @@ void ArgsManager::SetConfigFilePath(fs::path path) m_config_path = path; } +fs::path ArgsManager::GetRWConfigFilePath() const +{ + LOCK(cs_args); + return *Assert(m_rwconf_path); +} + ChainType ArgsManager::GetChainType() const { std::variant arg = GetChainArg(); @@ -819,6 +828,217 @@ void ArgsManager::LogArgs() const logArgsPrefix("Command-line arg:", "", m_settings.command_line_options); } +namespace { + + // Like std::getline, but includes the EOL character in the result + bool getline_with_eol(std::istream& stream, std::string& result) + { + int current_char; + current_char = stream.get(); + if (current_char == std::char_traits::eof()) { + return false; + } + result.clear(); + result.push_back(char(current_char)); + while (current_char != '\n') { + current_char = stream.get(); + if (current_char == std::char_traits::eof()) { + break; + } + result.push_back(char(current_char)); + } + return true; + } + + const char * const ModifyRWConfigFile_ws_chars = " \t\r\n"; + + void ModifyRWConfigFile_SanityCheck(const std::string& s) + { + if (s.empty()) { + // Dereferencing .begin or .rbegin below is invalid unless the string has at least one character. + return; + } + + static const char * const newline_chars = "\r\n"; + static std::string ws_chars(ModifyRWConfigFile_ws_chars); + if (s.find_first_of(newline_chars) != std::string::npos) { + throw std::invalid_argument("New-line in config name/value"); + } + if (ws_chars.find(*s.begin()) != std::string::npos || ws_chars.find(*s.rbegin()) != std::string::npos) { + throw std::invalid_argument("Config name/value has leading/trailing whitespace"); + } + } + + void ModifyRWConfigFile_WriteRemaining(std::ostream& stream_out, const std::map& settings_to_change, std::set& setFound) + { + for (const auto& setting_pair : settings_to_change) { + const std::string& key = setting_pair.first; + const std::string& val = setting_pair.second; + if (setFound.find(key) != setFound.end()) { + continue; + } + setFound.insert(key); + ModifyRWConfigFile_SanityCheck(key); + ModifyRWConfigFile_SanityCheck(val); + stream_out << key << "=" << val << "\n"; + } + } +} // namespace + +void ModifyRWConfigStream(std::istream& stream_in, std::ostream& stream_out, const std::map& settings_to_change) +{ + static const char * const ws_chars = ModifyRWConfigFile_ws_chars; + std::set setFound; + std::string s, lineend, linebegin, key; + std::string::size_type n, n2; + bool inside_group = false, have_eof_nl = true; + std::map::const_iterator iterCS; + size_t lineno = 0; + while (getline_with_eol(stream_in, s)) { + ++lineno; + + have_eof_nl = (!s.empty()) && (*s.rbegin() == '\n'); + n = s.find('#'); + const bool has_comment = (n != std::string::npos); + if (!has_comment) { + n = s.size(); + } + if (n > 0) { + n2 = s.find_last_not_of(ws_chars, n - 1); + if (n2 != std::string::npos) { + n = n2 + 1; + } + } + n2 = s.find_first_not_of(ws_chars); + if (n2 == std::string::npos || n2 >= n) { + // Blank or comment-only line + stream_out << s; + continue; + } + lineend = s.substr(n); + linebegin = s.substr(0, n2); + s = s.substr(n2, n - n2); + + // It is impossible for s to be empty here, due to the blank line check above + if (*s.begin() == '[' && *s.rbegin() == ']') { + // We don't use sections, so we could possibly just write out the rest of the file - but we need to check for unparsable lines, so we just set a flag to ignore settings from here on + ModifyRWConfigFile_WriteRemaining(stream_out, settings_to_change, setFound); + inside_group = true; + key.clear(); + + stream_out << linebegin << s << lineend; + continue; + } + + n = s.find('='); + if (n == std::string::npos) { + // Bad line; this causes boost to throw an exception when parsing, so we comment out the entire file + stream_in.seekg(0, std::ios_base::beg); + stream_out.seekp(0, std::ios_base::beg); + if (!(stream_in.good() && stream_out.good())) { + throw std::ios_base::failure("Failed to rewind (to comment out existing file)"); + } + // First, write out all the settings we intend to set + setFound.clear(); + ModifyRWConfigFile_WriteRemaining(stream_out, settings_to_change, setFound); + // We then define a category to ensure new settings get added before the invalid stuff + stream_out << "[INVALID]\n"; + // Then, describe the problem in a comment + stream_out << "# Error parsing line " << lineno << ": " << s << "\n"; + // Finally, dump the rest of the file commented out + while (getline_with_eol(stream_in, s)) { + stream_out << "#" << s; + } + return; + } + + if (!inside_group) { + // We don't support/use groups, so once we're inside key is always null to avoid setting anything + n2 = s.find_last_not_of(ws_chars, n - 1); + if (n2 == std::string::npos) { + n2 = n - 1; + } else { + ++n2; + } + key = s.substr(0, n2); + } + if ((!key.empty()) && (iterCS = settings_to_change.find(key)) != settings_to_change.end() && setFound.find(key) == setFound.end()) { + // This is the key we want to change + const std::string& val = iterCS->second; + setFound.insert(key); + ModifyRWConfigFile_SanityCheck(val); + if (has_comment) { + // Rather than change a commented line, comment it out entirely (the existing comment may relate to the value) and replace it + stream_out << key << "=" << val << "\n"; + linebegin.insert(linebegin.begin(), '#'); + } else { + // Just modify the value in-line otherwise + n2 = s.find_first_not_of(ws_chars, n + 1); + if (n2 == std::string::npos) { + n2 = n + 1; + } + s = s.substr(0, n2) + val; + } + } + stream_out << linebegin << s << lineend; + } + if (setFound.size() < settings_to_change.size()) { + if (!have_eof_nl) { + stream_out << "\n"; + } + ModifyRWConfigFile_WriteRemaining(stream_out, settings_to_change, setFound); + } +} + +void ArgsManager::ModifyRWConfigFile(const std::map& settings_to_change) +{ + LOCK(cs_args); + fs::path rwconf_path{GetRWConfigFilePath()}; + fs::path rwconf_new_path{rwconf_path}; + rwconf_new_path += ".new"; + try { + fs::remove(rwconf_new_path); + std::ofstream streamRWConfigOut(rwconf_new_path, std::ios_base::out | std::ios_base::trunc); + if (fs::exists(rwconf_path)) { + std::ifstream streamRWConfig(rwconf_path); + ::ModifyRWConfigStream(streamRWConfig, streamRWConfigOut, settings_to_change); + } else { + std::istringstream streamIn; + ::ModifyRWConfigStream(streamIn, streamRWConfigOut, settings_to_change); + } + } catch (...) { + fs::remove(rwconf_new_path); + throw; + } + if (!RenameOver(rwconf_new_path, rwconf_path)) { + fs::remove(rwconf_new_path); + throw std::ios_base::failure(strprintf("Failed to replace %s", fs::PathToString(rwconf_new_path))); + } +} + +void ArgsManager::ModifyRWConfigFile(const std::string& setting_to_change, const std::string& new_value) +{ + std::map settings_to_change; + settings_to_change[setting_to_change] = new_value; + ModifyRWConfigFile(settings_to_change); +} + +void ArgsManager::EraseRWConfigFile() +{ + LOCK(cs_args); + fs::path rwconf_path{GetRWConfigFilePath()}; + if (!fs::exists(rwconf_path)) { + return; + } + fs::path rwconf_reset_path = rwconf_path; + rwconf_reset_path += ".reset"; + if (!RenameOver(rwconf_path, rwconf_reset_path)) { + if (fs::remove(rwconf_path)) { + throw std::ios_base::failure(strprintf("Failed to remove %s", fs::PathToString(rwconf_path))); + } + } +} + namespace common { #ifdef WIN32 WinCmdLineArgs::WinCmdLineArgs() diff --git a/src/common/args.h b/src/common/args.h index 0a94a74d50..dd182a7f39 100644 --- a/src/common/args.h +++ b/src/common/args.h @@ -25,6 +25,7 @@ class ArgsManager; extern const char * const BITCOIN_CONF_FILENAME; extern const char * const BITCOIN_SETTINGS_FILENAME; +extern const char * const BITCOIN_RW_CONF_FILENAME; // Return true if -datadir option points to a valid directory or is not specified. bool CheckDataDirOption(const ArgsManager& args); @@ -93,6 +94,8 @@ std::optional SettingToInt(const common::SettingsValue&); bool SettingToBool(const common::SettingsValue&, bool); std::optional SettingToBool(const common::SettingsValue&); +void ModifyRWConfigStream(std::istream& stream_in, std::ostream& stream_out, const std::map& settings_to_change); + class ArgsManager { public: @@ -138,6 +141,7 @@ protected: bool m_accept_any_command GUARDED_BY(cs_args){true}; std::list m_config_sections GUARDED_BY(cs_args); std::optional m_config_path GUARDED_BY(cs_args); + std::optional m_rwconf_path GUARDED_BY(cs_args); mutable fs::path m_cached_blocks_path GUARDED_BY(cs_args); mutable fs::path m_cached_datadir_path GUARDED_BY(cs_args); mutable fs::path m_cached_network_datadir_path GUARDED_BY(cs_args); @@ -181,8 +185,13 @@ protected: */ fs::path GetConfigFilePath() const; void SetConfigFilePath(fs::path); + fs::path GetRWConfigFilePath() const; [[nodiscard]] bool ReadConfigFiles(std::string& error, bool ignore_invalid_keys = false); + void ModifyRWConfigFile(const std::map& settings_to_change); + void ModifyRWConfigFile(const std::string& setting_to_change, const std::string& new_value); + void EraseRWConfigFile(); + /** * Log warnings for options in m_section_only_args when * they are specified in the default section but not overridden diff --git a/src/common/config.cpp b/src/common/config.cpp index 4b0f45f868..107995ff3d 100644 --- a/src/common/config.cpp +++ b/src/common/config.cpp @@ -218,6 +218,17 @@ bool ArgsManager::ReadConfigFiles(std::string& error, bool ignore_invalid_keys) error = strprintf("specified data directory \"%s\" does not exist.", GetArg("-datadir", "")); return false; } + + LOCK(cs_args); + m_rwconf_path = AbsPathForConfigVal(*this, GetPathArg("-confrw", BITCOIN_RW_CONF_FILENAME)); + const auto rwconf_path{GetRWConfigFilePath()}; + std::ifstream rwconf_stream(rwconf_path); + if (rwconf_stream.good()) { + if (!ReadConfigStream(rwconf_stream, fs::PathToString(rwconf_path), error, ignore_invalid_keys, &m_settings.rw_config)) { + return false; + } + } + return true; } diff --git a/src/init.cpp b/src/init.cpp index d98937133d..11d50c40ce 100644 --- a/src/init.cpp +++ b/src/init.cpp @@ -476,6 +476,7 @@ void SetupServerArgs(ArgsManager& argsman) argsman.AddArg("-blocksonly", strprintf("Whether to reject transactions from network peers. Automatic broadcast and rebroadcast of any transactions from inbound peers is disabled, unless the peer has the 'forcerelay' permission. RPC transactions are not affected. (default: %u)", DEFAULT_BLOCKSONLY), ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS); argsman.AddArg("-coinstatsindex", strprintf("Maintain coinstats index used by the gettxoutsetinfo RPC (default: %u)", DEFAULT_COINSTATSINDEX), ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS); argsman.AddArg("-conf=", strprintf("Specify path to read-only configuration file. Relative paths will be prefixed by datadir location (only useable from command line, not configuration file) (default: %s)", BITCOIN_CONF_FILENAME), ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS); + argsman.AddArg("-confrw=", strprintf("Specify read/write configuration file. Relative paths will be prefixed by the network-specific datadir location (default: %s)", BITCOIN_RW_CONF_FILENAME), ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS); argsman.AddArg("-datadir=", "Specify data directory", ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS); argsman.AddArg("-dbbatchsize", strprintf("Maximum database write batch size in bytes (default: %u)", nDefaultDbBatchSize), ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::OPTIONS); argsman.AddArg("-dbcache=", strprintf("Maximum database cache size MiB (%d to %d, default: %d). In addition, unused mempool memory is shared for this cache (see -maxmempool).", nMinDbCache, nMaxDbCache, nDefaultDbCache), ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS); diff --git a/src/init/common.cpp b/src/init/common.cpp index 0800cd93d8..f259c98a24 100644 --- a/src/init/common.cpp +++ b/src/init/common.cpp @@ -138,6 +138,15 @@ bool StartLogging(const ArgsManager& args) LogPrintf("Config file: %s (not found, skipping)\n", fs::PathToString(config_file_path)); } + fs::path rwconfig_file_path = args.GetRWConfigFilePath(); + if (fs::exists(rwconfig_file_path)) { + LogPrintf("R/W Config file: %s\n", fs::PathToString(rwconfig_file_path)); + } else if (gArgs.IsArgSet("-confrw")) { + InitWarning(strprintf(_("The specified R/W config file %s does not exist"), fs::PathToString(rwconfig_file_path))); + } else { + LogPrintf("R/W Config file: %s (not found, skipping)\n", fs::PathToString(rwconfig_file_path)); + } + // Log the config arguments to debug.log args.LogArgs(); diff --git a/src/qt/optionsmodel.cpp b/src/qt/optionsmodel.cpp index d816a72ca3..a7afe44e40 100644 --- a/src/qt/optionsmodel.cpp +++ b/src/qt/optionsmodel.cpp @@ -296,6 +296,9 @@ void OptionsModel::Reset() QString dataDir = GUIUtil::getDefaultDataDirectory(); dataDir = settings.value("strDataDir", dataDir).toString(); + // Remove rw config file + gArgs.EraseRWConfigFile(); + // Remove all entries from our QSettings object settings.clear(); diff --git a/src/qt/test/apptests.cpp b/src/qt/test/apptests.cpp index 10abcb00eb..d176ea5ae9 100644 --- a/src/qt/test/apptests.cpp +++ b/src/qt/test/apptests.cpp @@ -5,6 +5,7 @@ #include #include +#include #include #include #include @@ -66,6 +67,14 @@ void AppTests::appTests() } #endif + { + // Need to ensure datadir is setup so resetting settings can delete the non-existent bitcoin_rw.conf + std::string error; + if (!gArgs.ReadConfigFiles(error, true)) { + QWARN("Error in readConfigFiles"); + } + } + qRegisterMetaType("interfaces::BlockAndHeaderTipInfo"); m_app.parameterSetup(); QVERIFY(m_app.createOptionsModel(/*resetSettings=*/true)); diff --git a/src/test/util_tests.cpp b/src/test/util_tests.cpp index 47808a2a58..3f3ce7701c 100644 --- a/src/test/util_tests.cpp +++ b/src/test/util_tests.cpp @@ -1815,4 +1815,157 @@ BOOST_AUTO_TEST_CASE(clearshrink_test) } } +static std::string CheckModifyRWConfigFile(std::map& settings_to_change, const std::string& current_config_file) +{ + std::istringstream stream_in(current_config_file); + std::ostringstream stream_out; + try { + ModifyRWConfigStream(stream_in, stream_out, settings_to_change); + } catch (...) { + settings_to_change.clear(); + throw; + } + settings_to_change.clear(); + return stream_out.str(); +} + +BOOST_AUTO_TEST_CASE(test_ModifyRWConfigFile) +{ + std::map cs; + + BOOST_CHECK_EQUAL(CheckModifyRWConfigFile(cs, "a=b"), "a=b"); + + cs["a"] = "c"; + BOOST_CHECK_EQUAL(CheckModifyRWConfigFile(cs, "a=b"), "a=c"); + BOOST_CHECK(cs.empty()); + + // Multi-char name/value + cs["ab"] = "cd"; + BOOST_CHECK_EQUAL(CheckModifyRWConfigFile(cs, "ab=bc"), "ab=cd"); + + // Preserved final newline + BOOST_CHECK_EQUAL(CheckModifyRWConfigFile(cs, "a=b\n"), "a=b\n"); + cs["a"] = "c"; + BOOST_CHECK_EQUAL(CheckModifyRWConfigFile(cs, "a=b\n"), "a=c\n"); + + // Preserved final tab + BOOST_CHECK_EQUAL(CheckModifyRWConfigFile(cs, "a=b\t"), "a=b\t"); + cs["a"] = "c"; + BOOST_CHECK_EQUAL(CheckModifyRWConfigFile(cs, "a=b\t"), "a=c\t"); + + // Preserved final space + BOOST_CHECK_EQUAL(CheckModifyRWConfigFile(cs, "a=b "), "a=b "); + cs["a"] = "c"; + BOOST_CHECK_EQUAL(CheckModifyRWConfigFile(cs, "a=b "), "a=c "); + + // Preserved final crnl + BOOST_CHECK_EQUAL(CheckModifyRWConfigFile(cs, "a=b\r\n"), "a=b\r\n"); + cs["a"] = "c"; + BOOST_CHECK_EQUAL(CheckModifyRWConfigFile(cs, "a=b\r\n"), "a=c\r\n"); + + // Empty file + cs["a"] = "c"; + BOOST_CHECK_EQUAL(CheckModifyRWConfigFile(cs, ""), "a=c\n"); + + // Ignore k=v in comment + cs["a"] = "c"; + BOOST_CHECK_EQUAL(CheckModifyRWConfigFile(cs, "#a=b"), "#a=b\na=c\n"); + + // Preserved comment + BOOST_CHECK_EQUAL(CheckModifyRWConfigFile(cs, "a=b\t# c"), "a=b\t# c"); + + // Commented out commented value + cs["a"] = "c"; + BOOST_CHECK_EQUAL(CheckModifyRWConfigFile(cs, "a=b\t# c"), "a=c\n#a=b\t# c"); + + // Preserved whitespace before name + BOOST_CHECK_EQUAL(CheckModifyRWConfigFile(cs, " \t \ta=b"), " \t \ta=b"); + cs["a"] = "c"; + BOOST_CHECK_EQUAL(CheckModifyRWConfigFile(cs, " \t \ta=b"), " \t \ta=c"); + + // Preserved whitespace after name + BOOST_CHECK_EQUAL(CheckModifyRWConfigFile(cs, "a \t \t=b"), "a \t \t=b"); + cs["a"] = "c"; + BOOST_CHECK_EQUAL(CheckModifyRWConfigFile(cs, "a \t \t=b"), "a \t \t=c"); + + // Preserved whitespace before value + BOOST_CHECK_EQUAL(CheckModifyRWConfigFile(cs, "a= \t \tb"), "a= \t \tb"); + cs["a"] = "c"; + BOOST_CHECK_EQUAL(CheckModifyRWConfigFile(cs, "a= \t \tb"), "a= \t \tc"); + + // Modifying value between others + BOOST_CHECK_EQUAL(CheckModifyRWConfigFile(cs, "a=b\nab=bc\nd=e"), "a=b\nab=bc\nd=e"); + cs["ab"] = "x"; + BOOST_CHECK_EQUAL(CheckModifyRWConfigFile(cs, "a=b\nab=bc\nd=e"), "a=b\nab=x\nd=e"); + + // Blank key/value + cs["ab"] = ""; + BOOST_CHECK_EQUAL(CheckModifyRWConfigFile(cs, "a=b\nab=bc\nd=e"), "a=b\nab=\nd=e"); + cs[""] = "x"; + BOOST_CHECK_EQUAL(CheckModifyRWConfigFile(cs, "a=b\nab=bc\nd=e"), "a=b\nab=bc\nd=e\n=x\n"); + + // Blank line in source + BOOST_CHECK_EQUAL(CheckModifyRWConfigFile(cs, "a=b\n\nab=bc\n\nd=e"), "a=b\n\nab=bc\n\nd=e"); + cs["ab"] = "x"; + BOOST_CHECK_EQUAL(CheckModifyRWConfigFile(cs, "a=b\n\nab=bc\n\nd=e"), "a=b\n\nab=x\n\nd=e"); + + // Duplicate keys in the source + BOOST_CHECK_EQUAL(CheckModifyRWConfigFile(cs, "a=b\nab=bc\nf=x\nab=zx\nd=e"), "a=b\nab=bc\nf=x\nab=zx\nd=e"); + cs["ab"] = "x"; + BOOST_CHECK_EQUAL(CheckModifyRWConfigFile(cs, "a=b\nab=bc\nf=x\nab=zx\nd=e"), "a=b\nab=x\nf=x\nab=zx\nd=e"); + + // Comment out entire file if invalid input line + BOOST_CHECK_EQUAL(CheckModifyRWConfigFile(cs, "a=b\nab=bc\nGARBAGE\nd=e"), "[INVALID]\n# Error parsing line 3: GARBAGE\n#a=b\n#ab=bc\n#GARBAGE\n#d=e"); + cs["ab"] = "x"; + BOOST_CHECK_EQUAL(CheckModifyRWConfigFile(cs, "a=b\nab=bc\nGARBAGE\nd=e"), "ab=x\n[INVALID]\n# Error parsing line 3: GARBAGE\n#a=b\n#ab=bc\n#GARBAGE\n#d=e"); + cs["ab"] = "x"; + BOOST_CHECK_EQUAL(CheckModifyRWConfigFile(cs, "a=b\nab=bc\nGARBAGE\nd=e\n"), "ab=x\n[INVALID]\n# Error parsing line 3: GARBAGE\n#a=b\n#ab=bc\n#GARBAGE\n#d=e\n"); + + // Whitespace inside values + BOOST_CHECK_EQUAL(CheckModifyRWConfigFile(cs, "a=b\nab=b\t \t c\nd=e"), "a=b\nab=b\t \t c\nd=e"); + cs["ab"] = "x \t \tx"; + BOOST_CHECK_EQUAL(CheckModifyRWConfigFile(cs, "a=b\nab=b\t \t c\nd=e"), "a=b\nab=x \t \tx\nd=e"); + + // Newline inside name/value + cs["a"] = "x\nx"; + BOOST_REQUIRE_THROW(CheckModifyRWConfigFile(cs, ""), std::invalid_argument); + cs["a"] = "x\rx"; + BOOST_REQUIRE_THROW(CheckModifyRWConfigFile(cs, ""), std::invalid_argument); + cs["a\nb"] = "x"; + BOOST_REQUIRE_THROW(CheckModifyRWConfigFile(cs, ""), std::invalid_argument); + cs["a\rb"] = "x"; + BOOST_REQUIRE_THROW(CheckModifyRWConfigFile(cs, ""), std::invalid_argument); + + // Whitespace leading/trailing name/value + cs["a"] = " x"; + BOOST_REQUIRE_THROW(CheckModifyRWConfigFile(cs, ""), std::invalid_argument); + cs["a"] = "\tx"; + BOOST_REQUIRE_THROW(CheckModifyRWConfigFile(cs, ""), std::invalid_argument); + cs[" a"] = "x"; + BOOST_REQUIRE_THROW(CheckModifyRWConfigFile(cs, ""), std::invalid_argument); + cs["\ta"] = "x"; + BOOST_REQUIRE_THROW(CheckModifyRWConfigFile(cs, ""), std::invalid_argument); + cs["a"] = "x "; + BOOST_REQUIRE_THROW(CheckModifyRWConfigFile(cs, ""), std::invalid_argument); + cs["a"] = "x\t"; + BOOST_REQUIRE_THROW(CheckModifyRWConfigFile(cs, ""), std::invalid_argument); + cs["a "] = "x"; + BOOST_REQUIRE_THROW(CheckModifyRWConfigFile(cs, ""), std::invalid_argument); + cs["a\t"] = "x"; + BOOST_REQUIRE_THROW(CheckModifyRWConfigFile(cs, ""), std::invalid_argument); + + // Ignore groups + BOOST_CHECK_EQUAL(CheckModifyRWConfigFile(cs, "a=b\n[group]\nab=bc\nd=e"), "a=b\n[group]\nab=bc\nd=e"); + cs["ab"] = "x"; + BOOST_CHECK_EQUAL(CheckModifyRWConfigFile(cs, "a=b\n[group]\nab=bc\nd=e"), "a=b\nab=x\n[group]\nab=bc\nd=e"); + BOOST_CHECK_EQUAL(CheckModifyRWConfigFile(cs, "a=b\n\t [group] \t#c\nab=bc\nd=e"), "a=b\n\t [group] \t#c\nab=bc\nd=e"); + cs["ab"] = "x"; + BOOST_CHECK_EQUAL(CheckModifyRWConfigFile(cs, "a=b\n\t [group] \t#c\nab=bc\nd=e"), "a=b\nab=x\n\t [group] \t#c\nab=bc\nd=e"); + + // Comment out entire file if invalid input line, even after a group + BOOST_CHECK_EQUAL(CheckModifyRWConfigFile(cs, "a=b\n[group]\nab=bc\nGARBAGE\nd=e"), "[INVALID]\n# Error parsing line 4: GARBAGE\n#a=b\n#[group]\n#ab=bc\n#GARBAGE\n#d=e"); + cs["ab"] = "x"; + BOOST_CHECK_EQUAL(CheckModifyRWConfigFile(cs, "a=b\n[group]\nab=bc\nGARBAGE\nd=e"), "ab=x\n[INVALID]\n# Error parsing line 4: GARBAGE\n#a=b\n#[group]\n#ab=bc\n#GARBAGE\n#d=e"); +} + BOOST_AUTO_TEST_SUITE_END() From 73db739d93d2a2084e408a250ddcfa5d5fe21887 Mon Sep 17 00:00:00 2001 From: Luke Dashjr Date: Tue, 15 Dec 2020 18:21:47 +0000 Subject: [PATCH 5/6] util/system: If settings.json is enabled, store rwconf changes there too This gets us Core (0.21+) compatibility --- src/common/args.cpp | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/common/args.cpp b/src/common/args.cpp index 94e7457f8b..480bcd6a9f 100644 --- a/src/common/args.cpp +++ b/src/common/args.cpp @@ -1014,6 +1014,13 @@ void ArgsManager::ModifyRWConfigFile(const std::map& s fs::remove(rwconf_new_path); throw std::ios_base::failure(strprintf("Failed to replace %s", fs::PathToString(rwconf_new_path))); } + if (!IsArgNegated("-settings")) { + // Also save to settings.json for Core (0.21+) compatibility + for (const auto& setting_change : settings_to_change) { + m_settings.rw_settings[setting_change.first] = setting_change.second; + } + WriteSettingsFile(); + } } void ArgsManager::ModifyRWConfigFile(const std::string& setting_to_change, const std::string& new_value) From 7c812c86d4bb3dfb41db1cadc9e3266271e5459e Mon Sep 17 00:00:00 2001 From: Luke Dashjr Date: Tue, 19 Mar 2024 05:01:08 +0000 Subject: [PATCH 6/6] Bugfix: rwconf: Update internal setting when modifying file Without this, the GUI will re-read the rwconf prune value each load of Settings --- src/common/args.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/common/args.cpp b/src/common/args.cpp index 480bcd6a9f..6a062ce0c2 100644 --- a/src/common/args.cpp +++ b/src/common/args.cpp @@ -1014,6 +1014,9 @@ void ArgsManager::ModifyRWConfigFile(const std::map& s fs::remove(rwconf_new_path); throw std::ios_base::failure(strprintf("Failed to replace %s", fs::PathToString(rwconf_new_path))); } + for (const auto& setting_change : settings_to_change) { + m_settings.rw_config[setting_change.first] = {setting_change.second}; + } if (!IsArgNegated("-settings")) { // Also save to settings.json for Core (0.21+) compatibility for (const auto& setting_change : settings_to_change) {