diff --git a/doc/man/bitcoin-qt.1 b/doc/man/bitcoin-qt.1 index ce252612e57..68f4dbc4e99 100644 --- a/doc/man/bitcoin-qt.1 +++ b/doc/man/bitcoin-qt.1 @@ -44,6 +44,10 @@ Specify configuration file (default: bitcoin.conf) .IP Specify data directory .HP +\fB\-includeconf=\fR +.IP +Specify additional configuration file, relative to the \fB\-datadir\fR path +.HP \fB\-dbcache=\fR .IP Set database cache size in megabytes (4 to 16384, default: 300) diff --git a/doc/man/bitcoind.1 b/doc/man/bitcoind.1 index fb066e0c6f3..18586b0ed93 100644 --- a/doc/man/bitcoind.1 +++ b/doc/man/bitcoind.1 @@ -49,6 +49,10 @@ Run in the background as a daemon and accept commands .IP Specify data directory .HP +\fB\-includeconf=\fR +.IP +Specify additional configuration file, relative to the \fB\-datadir\fR path +.HP \fB\-dbcache=\fR .IP Set database cache size in megabytes (4 to 16384, default: 300) diff --git a/src/bitcoin-cli.cpp b/src/bitcoin-cli.cpp index 8bec0289f56..dbf0a85ef08 100644 --- a/src/bitcoin-cli.cpp +++ b/src/bitcoin-cli.cpp @@ -104,7 +104,7 @@ static int AppInitRPC(int argc, char* argv[]) return EXIT_FAILURE; } try { - gArgs.ReadConfigFile(gArgs.GetArg("-conf", BITCOIN_CONF_FILENAME)); + gArgs.ReadConfigFiles(); } catch (const std::exception& e) { fprintf(stderr,"Error reading configuration file: %s\n", e.what()); return EXIT_FAILURE; diff --git a/src/bitcoind.cpp b/src/bitcoind.cpp index 543eba0e69d..2dbe7febdee 100644 --- a/src/bitcoind.cpp +++ b/src/bitcoind.cpp @@ -103,7 +103,7 @@ bool AppInit(int argc, char* argv[]) } try { - gArgs.ReadConfigFile(gArgs.GetArg("-conf", BITCOIN_CONF_FILENAME)); + gArgs.ReadConfigFiles(); } catch (const std::exception& e) { fprintf(stderr,"Error reading configuration file: %s\n", e.what()); return false; diff --git a/src/init.cpp b/src/init.cpp index f9a63348fdd..2c60dbb3cc7 100644 --- a/src/init.cpp +++ b/src/init.cpp @@ -355,6 +355,7 @@ std::string HelpMessage(HelpMessageMode mode) if (showDebug) { strUsage += HelpMessageOpt("-dbbatchsize", strprintf("Maximum database write batch size in bytes (default: %u)", nDefaultDbBatchSize)); } + strUsage += HelpMessageOpt("-includeconf=", _("Specify additional configuration file, relative to the -datadir path")); strUsage += HelpMessageOpt("-dbcache=", strprintf(_("Set database cache size in megabytes (%d to %d, default: %d)"), nMinDbCache, nMaxDbCache, nDefaultDbCache)); if (showDebug) strUsage += HelpMessageOpt("-feefilter", strprintf("Tell other nodes to filter invs to us by our mempool min fee (default: %u)", DEFAULT_FEEFILTER)); diff --git a/src/qt/bitcoin.cpp b/src/qt/bitcoin.cpp index 3fd58a2f9a0..dcaad9338ee 100644 --- a/src/qt/bitcoin.cpp +++ b/src/qt/bitcoin.cpp @@ -627,7 +627,7 @@ int main(int argc, char *argv[]) return EXIT_FAILURE; } try { - gArgs.ReadConfigFile(gArgs.GetArg("-conf", BITCOIN_CONF_FILENAME)); + gArgs.ReadConfigFiles(); } catch (const std::exception& e) { QMessageBox::critical(0, QObject::tr(PACKAGE_NAME), QObject::tr("Error: Cannot parse configuration file: %1. Only use key=value syntax.").arg(e.what())); diff --git a/src/qt/bitcoinstrings.cpp b/src/qt/bitcoinstrings.cpp index 9f798ccf623..7299dac151d 100644 --- a/src/qt/bitcoinstrings.cpp +++ b/src/qt/bitcoinstrings.cpp @@ -372,6 +372,7 @@ QT_TRANSLATE_NOOP("bitcoin-core", "Signing transaction failed"), QT_TRANSLATE_NOOP("bitcoin-core", "Specify configuration file (default: %s)"), QT_TRANSLATE_NOOP("bitcoin-core", "Specify connection timeout in milliseconds (minimum: 1, default: %d)"), QT_TRANSLATE_NOOP("bitcoin-core", "Specify data directory"), +QT_TRANSLATE_NOOP("bitcoin-core", "Specify additional configuration file, relative to the -datadir path"), QT_TRANSLATE_NOOP("bitcoin-core", "Specify pid file (default: %s)"), QT_TRANSLATE_NOOP("bitcoin-core", "Specify wallet file (within data directory)"), QT_TRANSLATE_NOOP("bitcoin-core", "Specify your own public address"), diff --git a/src/util.cpp b/src/util.cpp index ba563478fad..9c726d8fa22 100644 --- a/src/util.cpp +++ b/src/util.cpp @@ -598,26 +598,47 @@ fs::path GetConfigFile(const std::string& confPath) return pathConfigFile; } -void ArgsManager::ReadConfigFile(const std::string& confPath) +void ArgsManager::ReadConfigFile(fs::ifstream& streamConfig) +{ + std::set setOptions; + setOptions.insert("*"); + + for (boost::program_options::detail::config_file_iterator it(streamConfig, setOptions), end; it != end; ++it) { + // Don't overwrite existing settings so command line settings override bitcoin.conf + std::string strKey = std::string("-") + it->string_key; + std::string strValue = it->value[0]; + InterpretNegativeSetting(strKey, strValue); + if (mapArgs.count(strKey) == 0) { + mapArgs[strKey] = strValue; + } + mapMultiArgs[strKey].push_back(strValue); + } +} + +void ArgsManager::ReadConfigFiles() { + const std::string confPath = GetArg("-conf", BITCOIN_CONF_FILENAME); fs::ifstream streamConfig(GetConfigFile(confPath)); if (!streamConfig.good()) return; // No bitcoin.conf file is OK + // we do not allow -includeconf from command line, so we clear it here + mapArgs.erase("-includeconf"); + + std::string includeconf; { LOCK(cs_args); - std::set setOptions; - setOptions.insert("*"); - - for (boost::program_options::detail::config_file_iterator it(streamConfig, setOptions), end; it != end; ++it) - { - // Don't overwrite existing settings so command line settings override bitcoin.conf - std::string strKey = std::string("-") + it->string_key; - std::string strValue = it->value[0]; - InterpretNegativeSetting(strKey, strValue); - if (mapArgs.count(strKey) == 0) - mapArgs[strKey] = strValue; - mapMultiArgs[strKey].push_back(strValue); + ReadConfigFile(streamConfig); + if (mapArgs.count("-includeconf")) includeconf = mapArgs["-includeconf"]; + } + if (includeconf != "") { + LogPrintf("Attempting to include configuration file %s\n", includeconf.c_str()); + fs::ifstream includeConfig(GetConfigFile(includeconf)); + if (includeConfig.good()) { + LOCK(cs_args); + ReadConfigFile(includeConfig); + } else { + LogPrintf("Failed to include configuration file %s\n", includeconf.c_str()); } } // If datadir is changed in .conf file: diff --git a/src/util.h b/src/util.h index ead3d3d9351..54d307f8806 100644 --- a/src/util.h +++ b/src/util.h @@ -194,13 +194,15 @@ inline bool IsSwitchChar(char c) class ArgsManager { +private: + void ReadConfigFile(fs::ifstream& streamConfig); protected: CCriticalSection cs_args; std::map mapArgs; std::map > mapMultiArgs; public: void ParseParameters(int argc, const char*const argv[]); - void ReadConfigFile(const std::string& confPath); + void ReadConfigFiles(); std::vector GetArgs(const std::string& strArg); /** diff --git a/test/functional/includeconf.py b/test/functional/includeconf.py new file mode 100755 index 00000000000..e51321f9a36 --- /dev/null +++ b/test/functional/includeconf.py @@ -0,0 +1,39 @@ +#!/usr/bin/env python3 +# Copyright (c) 2017 The Bitcoin Core developers +# Distributed under the MIT software license, see the accompanying +# file COPYING or http://www.opensource.org/licenses/mit-license.php. +""" +Tests the includeconf directive + +Create an additional configuration file that is loaded from the main +bitcoin.conf via includeconf (done in setup_chain). We check that: +1. The files (bitcoin.conf and relative.conf) are indeed loaded. +2. The load ordering is correct (bitcoin.conf, then relative). +""" + +import os +from test_framework.test_framework import BitcoinTestFramework + +class IncludeConfTest(BitcoinTestFramework): + def setup_chain(self): + super().setup_chain() + # Create additional config file + # - tmpdir/node0/relative.conf + with open(os.path.join(self.options.tmpdir + "/node0", "relative.conf"), "w", encoding="utf8") as f: + f.write("uacomment=relative\n") + with open(os.path.join(self.options.tmpdir + "/node0", "bitcoin.conf"), 'a', encoding='utf8') as f: + f.write("uacomment=main\nincludeconf=relative.conf\n") + # subversion should end with "(main; relative)/" + + def __init__(self): + super().__init__() + self.setup_clean_chain = False + self.num_nodes = 1 + + def run_test(self): + nwinfo = self.nodes[0].getnetworkinfo() + subversion = nwinfo["subversion"] + assert subversion.endswith("main; relative)/") + +if __name__ == '__main__': + IncludeConfTest().main() diff --git a/test/functional/test_runner.py b/test/functional/test_runner.py index d248a6c0057..3220872e453 100755 --- a/test/functional/test_runner.py +++ b/test/functional/test_runner.py @@ -120,6 +120,7 @@ 'bip65-cltv-p2p.py', 'uptime.py', 'resendwallettransactions.py', + 'includeconf.py', ] EXTENDED_SCRIPTS = [