mirror of
https://github.com/Retropex/bitcoin.git
synced 2025-05-13 03:30:42 +02:00
Merge 11082 via rwconf-25+knots
This commit is contained in:
commit
0e725308ae
@ -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
|
||||
|
@ -79,6 +79,7 @@ static void SetupCliArgs(ArgsManager& argsman)
|
||||
|
||||
argsman.AddArg("-version", "Print version and exit", ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS);
|
||||
argsman.AddArg("-conf=<file>", 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=<file>", 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=<dir>", "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 "
|
||||
@ -171,13 +172,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.GetChainName());
|
||||
} catch (const std::exception& e) {
|
||||
tfm::format(std::cerr, "Error: %s\n", e.what());
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
return CONTINUE_EXECUTION;
|
||||
}
|
||||
|
||||
|
@ -441,6 +441,7 @@ void SetupServerArgs(ArgsManager& argsman)
|
||||
argsman.AddArg("-blocksonly", strprintf("Whether to reject transactions from network peers. Automatic broadcast and rebroadcast of any transactions from any peer is disabled, unless it 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=<file>", 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=<file>", 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=<dir>", "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=<n>", strprintf("Maximum database cache size <n> 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);
|
||||
|
@ -133,6 +133,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();
|
||||
|
||||
|
@ -313,6 +313,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();
|
||||
|
||||
|
@ -12,6 +12,7 @@
|
||||
#include <qt/rpcconsole.h>
|
||||
#include <shutdown.h>
|
||||
#include <test/util/setup_common.h>
|
||||
#include <util/system.h>
|
||||
#include <validation.h>
|
||||
|
||||
#if defined(HAVE_CONFIG_H)
|
||||
@ -70,6 +71,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>("interfaces::BlockAndHeaderTipInfo");
|
||||
m_app.parameterSetup();
|
||||
QVERIFY(m_app.createOptionsModel(/*resetSettings=*/true));
|
||||
|
@ -1816,4 +1816,157 @@ BOOST_AUTO_TEST_CASE(clearshrink_test)
|
||||
}
|
||||
}
|
||||
|
||||
static std::string CheckModifyRWConfigFile(std::map<std::string, std::string>& 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<std::string, std::string> 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()
|
||||
|
@ -19,6 +19,7 @@ namespace {
|
||||
enum class Source {
|
||||
FORCED,
|
||||
COMMAND_LINE,
|
||||
CONFIG_FILE_RW,
|
||||
RW_SETTINGS,
|
||||
CONFIG_FILE_NETWORK_SECTION,
|
||||
CONFIG_FILE_DEFAULT_SECTION
|
||||
@ -40,6 +41,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);
|
||||
@ -147,7 +152,7 @@ SettingsValue GetSetting(const Settings& settings,
|
||||
// the config file the precedence is reversed for all settings except
|
||||
// chain name 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_name;
|
||||
|
||||
// Weird behavior preserved for backwards compatibility: Negated
|
||||
@ -199,7 +204,7 @@ std::vector<SettingsValue> 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.
|
||||
|
@ -33,6 +33,8 @@ struct Settings {
|
||||
std::map<std::string, SettingsValue> forced_settings;
|
||||
//! Map of setting name to list of command line values.
|
||||
std::map<std::string, std::vector<SettingsValue>> command_line_options;
|
||||
//! Map of setting name to list of r/w config file values.
|
||||
std::map<std::string, std::vector<SettingsValue>> rw_config;
|
||||
//! Map of setting name to read-write file setting value.
|
||||
std::map<std::string, SettingsValue> rw_settings;
|
||||
//! Map of config section name and setting name to list of config file values.
|
||||
|
@ -56,12 +56,14 @@
|
||||
#include <system_error>
|
||||
#include <thread>
|
||||
#include <typeinfo>
|
||||
#include <unordered_set>
|
||||
|
||||
// Application startup time (used for uptime calculation)
|
||||
const int64_t nStartupTime = GetTime();
|
||||
|
||||
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;
|
||||
|
||||
@ -828,7 +830,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<std::string, std::vector<util::SettingsValue>>* settings_target)
|
||||
{
|
||||
LOCK(cs_args);
|
||||
std::vector<std::pair<std::string, std::string>> options;
|
||||
@ -844,6 +846,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) {
|
||||
@ -863,11 +868,18 @@ fs::path ArgsManager::GetConfigFilePath() const
|
||||
return *Assert(m_config_path);
|
||||
}
|
||||
|
||||
fs::path ArgsManager::GetRWConfigFilePath() const
|
||||
{
|
||||
LOCK(cs_args);
|
||||
return *Assert(m_rwconf_path);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
@ -947,12 +959,31 @@ 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.GetChainName());
|
||||
} catch (const std::exception& e) {
|
||||
error = e.what();
|
||||
return false;
|
||||
}
|
||||
|
||||
// If datadir is changed in .conf file:
|
||||
ClearPathCache();
|
||||
if (!CheckDataDirOption(*this)) {
|
||||
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;
|
||||
}
|
||||
|
||||
@ -1031,9 +1062,228 @@ 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);
|
||||
}
|
||||
|
||||
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<char>::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<char>::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<std::string, std::string>& settings_to_change, std::set<std::string>& 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<std::string, std::string>& settings_to_change)
|
||||
{
|
||||
static const char * const ws_chars = ModifyRWConfigFile_ws_chars;
|
||||
std::set<std::string> setFound;
|
||||
std::string s, lineend, linebegin, key;
|
||||
std::string::size_type n, n2;
|
||||
bool inside_group = false, have_eof_nl = true;
|
||||
std::map<std::string, std::string>::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<std::string, std::string>& 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)));
|
||||
}
|
||||
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)
|
||||
{
|
||||
std::map<std::string, std::string> 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)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::string ShellEscape(const std::string& arg)
|
||||
{
|
||||
std::string escaped = arg;
|
||||
|
@ -39,6 +39,7 @@ int64_t GetStartupTime();
|
||||
|
||||
extern const char * const BITCOIN_CONF_FILENAME;
|
||||
extern const char * const BITCOIN_SETTINGS_FILENAME;
|
||||
extern const char * const BITCOIN_RW_CONF_FILENAME;
|
||||
|
||||
void SetupEnvironment();
|
||||
bool SetupNetworking();
|
||||
@ -104,6 +105,8 @@ std::optional<int64_t> SettingToInt(const util::SettingsValue&);
|
||||
bool SettingToBool(const util::SettingsValue&, bool);
|
||||
std::optional<bool> SettingToBool(const util::SettingsValue&);
|
||||
|
||||
void ModifyRWConfigStream(std::istream& stream_in, std::ostream& stream_out, const std::map<std::string, std::string>& settings_to_change);
|
||||
|
||||
class ArgsManager
|
||||
{
|
||||
public:
|
||||
@ -149,11 +152,12 @@ protected:
|
||||
bool m_accept_any_command GUARDED_BY(cs_args){true};
|
||||
std::list<SectionInfo> m_config_sections GUARDED_BY(cs_args);
|
||||
std::optional<fs::path> m_config_path GUARDED_BY(cs_args);
|
||||
std::optional<fs::path> 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);
|
||||
|
||||
[[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<std::string, std::vector<util::SettingsValue>>* settings_target = nullptr);
|
||||
|
||||
/**
|
||||
* Returns true if settings values from the default section should be used,
|
||||
@ -191,8 +195,13 @@ protected:
|
||||
* Return config file path (read-only)
|
||||
*/
|
||||
fs::path GetConfigFilePath() const;
|
||||
fs::path GetRWConfigFilePath() const;
|
||||
[[nodiscard]] bool ReadConfigFiles(std::string& error, bool ignore_invalid_keys = false);
|
||||
|
||||
void ModifyRWConfigFile(const std::map<std::string, std::string>& 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
|
||||
|
Loading…
Reference in New Issue
Block a user