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 = [