diff --git a/doc/release-notes.md b/doc/release-notes.md
--- a/doc/release-notes.md
+++ b/doc/release-notes.md
@@ -3,3 +3,11 @@
This release includes the following features and fixes:
+ - `-includeconf=` can be used to include additional configuration files.
+ Only works inside the `bitcoin.conf` file, not inside included files or from
+ command-line. Multiple files may be included. Can be disabled from command-
+ line via `-noincludeconf`. Note that multi-argument commands like
+ `-includeconf` will override preceding `-noincludeconf`, i.e.
+ noincludeconf=1
+ includeconf=relative.conf
+ as bitcoin.conf will still include `relative.conf`.
diff --git a/src/bitcoin-cli.cpp b/src/bitcoin-cli.cpp
--- a/src/bitcoin-cli.cpp
+++ b/src/bitcoin-cli.cpp
@@ -155,7 +155,7 @@
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
--- a/src/bitcoind.cpp
+++ b/src/bitcoind.cpp
@@ -104,7 +104,7 @@
return false;
}
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
--- a/src/init.cpp
+++ b/src/init.cpp
@@ -383,6 +383,11 @@
"Unit is seconds (default: %d)",
DEFAULT_MIN_FINALIZATION_DELAY),
false, OptionsCategory::OPTIONS);
+ gArgs.AddArg(
+ "-includeconf=",
+ _("Specify additional configuration file, relative to the -datadir "
+ "path (only useable from configuration file, not command line)"),
+ false, OptionsCategory::OPTIONS);
gArgs.AddArg(
"-maxreorgdepth=",
strprintf("Configure at what depth blocks are considered final "
diff --git a/src/interfaces/node.h b/src/interfaces/node.h
--- a/src/interfaces/node.h
+++ b/src/interfaces/node.h
@@ -51,7 +51,7 @@
virtual bool softSetBoolArg(const std::string &arg, bool value) = 0;
//! Load settings from configuration file.
- virtual void readConfigFile(const std::string &conf_path) = 0;
+ virtual void readConfigFiles() = 0;
//! Choose network parameters.
virtual void selectParams(const std::string &network) = 0;
diff --git a/src/interfaces/node.cpp b/src/interfaces/node.cpp
--- a/src/interfaces/node.cpp
+++ b/src/interfaces/node.cpp
@@ -54,9 +54,7 @@
void parseParameters(int argc, const char *const argv[]) override {
gArgs.ParseParameters(argc, argv);
}
- void readConfigFile(const std::string &conf_path) override {
- gArgs.ReadConfigFile(conf_path);
- }
+ void readConfigFiles() override { gArgs.ReadConfigFiles(); }
bool softSetArg(const std::string &arg,
const std::string &value) override {
return gArgs.SoftSetArg(arg, value);
diff --git a/src/qt/bitcoin.cpp b/src/qt/bitcoin.cpp
--- a/src/qt/bitcoin.cpp
+++ b/src/qt/bitcoin.cpp
@@ -717,7 +717,7 @@
return EXIT_FAILURE;
}
try {
- node->readConfigFile(gArgs.GetArg("-conf", BITCOIN_CONF_FILENAME));
+ node->readConfigFiles();
} catch (const std::exception &e) {
QMessageBox::critical(
0, QObject::tr(PACKAGE_NAME),
diff --git a/src/util.h b/src/util.h
--- a/src/util.h
+++ b/src/util.h
@@ -145,7 +145,7 @@
void SelectConfigNetwork(const std::string &network);
void ParseParameters(int argc, const char *const argv[]);
- void ReadConfigFile(const std::string &confPath);
+ void ReadConfigFiles();
/**
* Log warnings for options in m_section_only_args when they are specified
diff --git a/src/util.cpp b/src/util.cpp
--- a/src/util.cpp
+++ b/src/util.cpp
@@ -470,6 +470,20 @@
m_override_args[key].push_back(val);
}
}
+
+ // we do not allow -includeconf from command line, so we clear it here
+ auto it = m_override_args.find("-includeconf");
+ if (it != m_override_args.end()) {
+ if (it->second.size() > 0) {
+ for (const auto &ic : it->second) {
+ fprintf(stderr,
+ "warning: -includeconf cannot be used from "
+ "commandline; ignoring -includeconf=%s\n",
+ ic.c_str());
+ }
+ m_override_args.erase(it);
+ }
+ }
}
std::vector ArgsManager::GetArgs(const std::string &strArg) const {
@@ -835,17 +849,50 @@
}
}
-void ArgsManager::ReadConfigFile(const std::string &confPath) {
+void ArgsManager::ReadConfigFiles() {
{
LOCK(cs_args);
m_config_args.clear();
}
+ const std::string confPath = GetArg("-conf", BITCOIN_CONF_FILENAME);
fs::ifstream stream(GetConfigFile(confPath));
// ok to not have a config file
if (stream.good()) {
ReadConfigStream(stream);
+ // if there is an -includeconf in the override args, but it is empty,
+ // that means the user passed '-noincludeconf' on the command line, in
+ // which case we should not include anything
+ bool emptyIncludeConf;
+ {
+ LOCK(cs_args);
+ emptyIncludeConf = m_override_args.count("-includeconf") == 0;
+ }
+ if (emptyIncludeConf) {
+ std::vector includeconf(GetArgs("-includeconf"));
+ {
+ // We haven't set m_network yet (that happens in
+ // SelectParams()), so manually check for network.includeconf
+ // args.
+ std::vector includeconf_net(GetArgs(
+ std::string("-") + GetChainName() + ".includeconf"));
+ includeconf.insert(includeconf.end(), includeconf_net.begin(),
+ includeconf_net.end());
+ }
+
+ for (const std::string &to_include : includeconf) {
+ fs::ifstream include_config(GetConfigFile(to_include));
+ if (include_config.good()) {
+ ReadConfigStream(include_config);
+ LogPrintf("Included configuration file %s\n",
+ to_include.c_str());
+ } else {
+ fprintf(stderr, "Failed to include configuration file %s\n",
+ to_include.c_str());
+ }
+ }
+ }
}
// If datadir is changed in .conf file:
diff --git a/test/functional/feature_includeconf.py b/test/functional/feature_includeconf.py
new file mode 100755
--- /dev/null
+++ b/test/functional/feature_includeconf.py
@@ -0,0 +1,86 @@
+#!/usr/bin/env python3
+# Copyright (c) 2018 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 argument
+
+Verify that:
+
+1. adding includeconf to the configuration file causes the includeconf
+ file to be loaded in the correct order.
+2. includeconf cannot be used as a command line argument.
+3. includeconf cannot be used recursively (ie includeconf can only
+ be used from the base config file).
+4. multiple includeconf arguments can be specified in the main config
+ file.
+"""
+import os
+import tempfile
+
+from test_framework.test_framework import BitcoinTestFramework, assert_equal
+
+
+class IncludeConfTest(BitcoinTestFramework):
+ def set_test_params(self):
+ self.setup_clean_chain = False
+ self.num_nodes = 1
+
+ def setup_chain(self):
+ super().setup_chain()
+ # Create additional config files
+ # - 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")
+ # - tmpdir/node0/relative2.conf
+ with open(os.path.join(self.options.tmpdir, "node0", "relative2.conf"), "w", encoding="utf8") as f:
+ f.write("uacomment=relative2\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")
+
+ def run_test(self):
+ self.log.info(
+ "-includeconf works from config file. subversion should end with 'main; relative)/'")
+
+ subversion = self.nodes[0].getnetworkinfo()["subversion"]
+ assert subversion.endswith("main; relative)/")
+
+ self.log.info(
+ "-includeconf cannot be used as command-line arg. subversion should still end with 'main; relative)/'")
+ self.stop_node(0)
+ with tempfile.SpooledTemporaryFile(max_size=2**16) as log_stderr:
+ self.start_node(
+ 0, extra_args=["-includeconf=relative2.conf"], stderr=log_stderr)
+
+ subversion = self.nodes[0].getnetworkinfo()["subversion"]
+ assert subversion.endswith("main; relative)/")
+ log_stderr.seek(0)
+ stderr = log_stderr.read().decode('utf-8').strip()
+ assert_equal(
+ stderr, 'warning: -includeconf cannot be used from commandline; ignoring -includeconf=relative2.conf')
+
+ self.log.info(
+ "-includeconf cannot be used recursively. subversion should end with 'main; relative)/'")
+ with open(os.path.join(self.options.tmpdir, "node0", "relative.conf"), "a", encoding="utf8") as f:
+ f.write("includeconf=relative2.conf\n")
+
+ self.restart_node(0)
+
+ subversion = self.nodes[0].getnetworkinfo()["subversion"]
+ assert subversion.endswith("main; relative)/")
+
+ self.log.info(
+ "multiple -includeconf args can be used from the base config file. subversion should end with 'main; relative; relative2)/'")
+ 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("includeconf=relative2.conf\n")
+
+ self.restart_node(0)
+
+ subversion = self.nodes[0].getnetworkinfo()["subversion"]
+ assert subversion.endswith("main; relative; relative2)/")
+
+
+if __name__ == '__main__':
+ IncludeConfTest().main()
diff --git a/test/functional/timing.json b/test/functional/timing.json
--- a/test/functional/timing.json
+++ b/test/functional/timing.json
@@ -103,6 +103,10 @@
"name": "feature_dersig.py",
"time": 3
},
+ {
+ "name": "feature_includeconf.py",
+ "time": 11
+ },
{
"name": "feature_logging.py",
"time": 17