Give an error and exit if there are unknown parameters

If an unknown option is given via either the command line args or
the conf file, throw an error and exit

Update tests for ArgsManager knowing args

Ignore unknown options in the config file for bitcoin-cli

Fix tests and bitcoin-cli to match actual options used
This commit is contained in:
Andrew Chow 2018-04-28 19:40:51 -04:00
parent 174f7c8080
commit 4f8704d57f
12 changed files with 205 additions and 66 deletions

View file

@ -33,13 +33,21 @@ static void SetupBenchArgs()
gArgs.AddArg("-plot-plotlyurl=<uri>", strprintf("URL to use for plotly.js (default: %s)", DEFAULT_PLOT_PLOTLYURL), false, OptionsCategory::OPTIONS);
gArgs.AddArg("-plot-width=<x>", strprintf("Plot width in pixel (default: %u)", DEFAULT_PLOT_WIDTH), false, OptionsCategory::OPTIONS);
gArgs.AddArg("-plot-height=<x>", strprintf("Plot height in pixel (default: %u)", DEFAULT_PLOT_HEIGHT), false, OptionsCategory::OPTIONS);
// Hidden
gArgs.AddArg("-h", "", false, OptionsCategory::HIDDEN);
gArgs.AddArg("-help", "", false, OptionsCategory::HIDDEN);
}
int
main(int argc, char** argv)
{
SetupBenchArgs();
gArgs.ParseParameters(argc, argv);
std::string error;
if (!gArgs.ParseParameters(argc, argv, error)) {
fprintf(stderr, "Error parsing command line arguments: %s\n", error.c_str());
return false;
}
if (HelpRequested(gArgs)) {
std::cout << gArgs.GetHelpMessage();

View file

@ -42,6 +42,7 @@ static void SetupCliArgs()
gArgs.AddArg("-named", strprintf("Pass named instead of positional arguments (default: %s)", DEFAULT_NAMED), false, OptionsCategory::OPTIONS);
gArgs.AddArg("-rpcclienttimeout=<n>", strprintf("Timeout in seconds during HTTP requests, or 0 for no timeout. (default: %d)", DEFAULT_HTTP_CLIENT_TIMEOUT), false, OptionsCategory::OPTIONS);
gArgs.AddArg("-rpcconnect=<ip>", strprintf("Send commands to node running on <ip> (default: %s)", DEFAULT_RPCCONNECT), false, OptionsCategory::OPTIONS);
gArgs.AddArg("-rpccookiefile=<loc>", _("Location of the auth cookie. Relative paths will be prefixed by a net-specific datadir location. (default: data dir)"), false, OptionsCategory::OPTIONS);
gArgs.AddArg("-rpcpassword=<pw>", "Password for JSON-RPC connections", false, OptionsCategory::OPTIONS);
gArgs.AddArg("-rpcport=<port>", strprintf("Connect to JSON-RPC on <port> (default: %u or testnet: %u)", defaultBaseParams->RPCPort(), testnetBaseParams->RPCPort()), false, OptionsCategory::OPTIONS);
gArgs.AddArg("-rpcuser=<user>", "Username for JSON-RPC connections", false, OptionsCategory::OPTIONS);
@ -49,6 +50,10 @@ static void SetupCliArgs()
gArgs.AddArg("-rpcwallet=<walletname>", "Send RPC for non-default wallet on RPC server (needs to exactly match corresponding -wallet option passed to bitcoind)", false, OptionsCategory::OPTIONS);
gArgs.AddArg("-stdin", "Read extra arguments from standard input, one per line until EOF/Ctrl-D (recommended for sensitive information such as passphrases). When combined with -stdinrpcpass, the first line from standard input is used for the RPC password.", false, OptionsCategory::OPTIONS);
gArgs.AddArg("-stdinrpcpass", strprintf("Read RPC password from standard input as a single line. When combined with -stdin, the first line from standard input is used for the RPC password."), false, OptionsCategory::OPTIONS);
// Hidden
gArgs.AddArg("-h", "", false, OptionsCategory::HIDDEN);
gArgs.AddArg("-help", "", false, OptionsCategory::HIDDEN);
}
//////////////////////////////////////////////////////////////////////////////
@ -80,7 +85,11 @@ static int AppInitRPC(int argc, char* argv[])
// Parameters
//
SetupCliArgs();
gArgs.ParseParameters(argc, argv);
std::string error;
if (!gArgs.ParseParameters(argc, argv, error)) {
fprintf(stderr, "Error parsing command line arguments: %s\n", error.c_str());
return EXIT_FAILURE;
}
if (argc < 2 || HelpRequested(gArgs) || gArgs.IsArgSet("-version")) {
std::string strUsage = strprintf("%s RPC client version", PACKAGE_NAME) + " " + FormatFullVersion() + "\n";
if (!gArgs.IsArgSet("-version")) {
@ -104,10 +113,8 @@ static int AppInitRPC(int argc, char* argv[])
fprintf(stderr, "Error: Specified data directory \"%s\" does not exist.\n", gArgs.GetArg("-datadir", "").c_str());
return EXIT_FAILURE;
}
try {
gArgs.ReadConfigFiles();
} catch (const std::exception& e) {
fprintf(stderr,"Error reading configuration file: %s\n", e.what());
if (!gArgs.ReadConfigFiles(error, true)) {
fprintf(stderr, "Error reading configuration file: %s\n", error.c_str());
return EXIT_FAILURE;
}
// Check for -testnet or -regtest parameter (BaseParams() calls are only valid after this clause)

View file

@ -64,6 +64,10 @@ static void SetupBitcoinTxArgs()
gArgs.AddArg("load=NAME:FILENAME", "Load JSON file FILENAME into register NAME", false, OptionsCategory::REGISTER_COMMANDS);
gArgs.AddArg("set=NAME:JSON-STRING", "Set register NAME to given JSON-STRING", false, OptionsCategory::REGISTER_COMMANDS);
// Hidden
gArgs.AddArg("-h", "", false, OptionsCategory::HIDDEN);
gArgs.AddArg("-help", "", false, OptionsCategory::HIDDEN);
}
//
@ -76,7 +80,11 @@ static int AppInitRawTx(int argc, char* argv[])
// Parameters
//
SetupBitcoinTxArgs();
gArgs.ParseParameters(argc, argv);
std::string error;
if (!gArgs.ParseParameters(argc, argv, error)) {
fprintf(stderr, "Error parsing command line arguments: %s\n", error.c_str());
return EXIT_FAILURE;
}
// Check for -testnet or -regtest parameter (Params() calls are only valid after this clause)
try {

View file

@ -65,7 +65,11 @@ static bool AppInit(int argc, char* argv[])
#if HAVE_DECL_DAEMON
gArgs.AddArg("-daemon", "Run in the background as a daemon and accept commands", false, OptionsCategory::OPTIONS);
#endif
gArgs.ParseParameters(argc, argv);
std::string error;
if (!gArgs.ParseParameters(argc, argv, error)) {
fprintf(stderr, "Error parsing command line arguments: %s\n", error.c_str());
return false;
}
// Process help and version before taking care about datadir
if (HelpRequested(gArgs) || gArgs.IsArgSet("-version")) {
@ -94,11 +98,8 @@ static bool AppInit(int argc, char* argv[])
fprintf(stderr, "Error: Specified data directory \"%s\" does not exist.\n", gArgs.GetArg("-datadir", "").c_str());
return false;
}
try
{
gArgs.ReadConfigFiles();
} catch (const std::exception& e) {
fprintf(stderr,"Error reading configuration file: %s\n", e.what());
if (!gArgs.ReadConfigFiles(error)) {
fprintf(stderr, "Error reading configuration file: %s\n", error.c_str());
return false;
}
// Check for -testnet or -regtest parameter (Params() calls are only valid after this clause)

View file

@ -499,6 +499,23 @@ void SetupServerArgs()
gArgs.AddArg("-rpcuser=<user>", "Username for JSON-RPC connections", false, OptionsCategory::RPC);
gArgs.AddArg("-rpcworkqueue=<n>", strprintf("Set the depth of the work queue to service RPC calls (default: %d)", DEFAULT_HTTP_WORKQUEUE), true, OptionsCategory::RPC);
gArgs.AddArg("-server", "Accept command line and JSON-RPC commands", false, OptionsCategory::RPC);
// Hidden options
gArgs.AddArg("-rpcssl", "", false, OptionsCategory::HIDDEN);
gArgs.AddArg("-benchmark", "", false, OptionsCategory::HIDDEN);
gArgs.AddArg("-h", "", false, OptionsCategory::HIDDEN);
gArgs.AddArg("-help", "", false, OptionsCategory::HIDDEN);
gArgs.AddArg("-socks", "", false, OptionsCategory::HIDDEN);
gArgs.AddArg("-tor", "", false, OptionsCategory::HIDDEN);
gArgs.AddArg("-debugnet", "", false, OptionsCategory::HIDDEN);
gArgs.AddArg("-whitelistalwaysrelay", "", false, OptionsCategory::HIDDEN);
gArgs.AddArg("-prematurewitness", "", false, OptionsCategory::HIDDEN);
gArgs.AddArg("-walletprematurewitness", "", false, OptionsCategory::HIDDEN);
gArgs.AddArg("-promiscuousmempoolflags", "", false, OptionsCategory::HIDDEN);
gArgs.AddArg("-blockminsize", "", false, OptionsCategory::HIDDEN);
gArgs.AddArg("-dbcrashratio", "", false, OptionsCategory::HIDDEN);
gArgs.AddArg("-forcecompactdb", "", false, OptionsCategory::HIDDEN);
gArgs.AddArg("-usehd", "", false, OptionsCategory::HIDDEN);
}
std::string LicenseInfo()

View file

@ -48,11 +48,11 @@ namespace {
class NodeImpl : public Node
{
void parseParameters(int argc, const char* const argv[]) override
bool parseParameters(int argc, const char* const argv[], std::string& error) override
{
gArgs.ParseParameters(argc, argv);
return gArgs.ParseParameters(argc, argv, error);
}
void readConfigFiles() override { gArgs.ReadConfigFiles(); }
bool readConfigFiles(std::string& error) override { return gArgs.ReadConfigFiles(error); }
bool softSetArg(const std::string& arg, const std::string& value) override { return gArgs.SoftSetArg(arg, value); }
bool softSetBoolArg(const std::string& arg, bool value) override { return gArgs.SoftSetBoolArg(arg, value); }
void selectParams(const std::string& network) override { SelectParams(network); }

View file

@ -38,7 +38,7 @@ public:
virtual ~Node() {}
//! Set command line arguments.
virtual void parseParameters(int argc, const char* const argv[]) = 0;
virtual bool parseParameters(int argc, const char* const argv[], std::string& error) = 0;
//! Set a command line argument if it doesn't already have a value
virtual bool softSetArg(const std::string& arg, const std::string& value) = 0;
@ -47,7 +47,7 @@ public:
virtual bool softSetBoolArg(const std::string& arg, bool value) = 0;
//! Load settings from configuration file.
virtual void readConfigFiles() = 0;
virtual bool readConfigFiles(std::string& error) = 0;
//! Choose network parameters.
virtual void selectParams(const std::string& network) = 0;

View file

@ -229,6 +229,9 @@ public:
/// Get window identifier of QMainWindow (BitcoinGUI)
WId getMainWinId() const;
/// Setup platform style
void setupPlatformStyle();
public Q_SLOTS:
void initializeResult(bool success);
void shutdownResult();
@ -315,10 +318,14 @@ BitcoinApplication::BitcoinApplication(interfaces::Node& node, int &argc, char *
paymentServer(0),
m_wallet_models(),
#endif
returnValue(0)
returnValue(0),
platformStyle(0)
{
setQuitOnLastWindowClosed(false);
}
void BitcoinApplication::setupPlatformStyle()
{
// UI per-platform customization
// This must be done inside the BitcoinApplication constructor, or after it, because
// PlatformStyle::instantiate requires a QApplication
@ -562,15 +569,9 @@ int main(int argc, char *argv[])
std::unique_ptr<interfaces::Node> node = interfaces::MakeNode();
/// 1. Parse command-line options. These take precedence over anything else.
// Command-line options take precedence:
node->setupServerArgs();
SetupUIArgs();
node->parseParameters(argc, argv);
// Do not refer to data directory yet, this can be overridden by Intro::pickDataDirectory
/// 2. Basic Qt initialization (not dependent on parameters or configuration)
/// 1. Basic Qt initialization (not dependent on parameters or configuration)
#if QT_VERSION < 0x050000
// Internal string conversion is all UTF-8
QTextCodec::setCodecForTr(QTextCodec::codecForName("UTF-8"));
@ -609,6 +610,20 @@ int main(int argc, char *argv[])
qRegisterMetaType<WalletModel*>("WalletModel*");
#endif
/// 2. Parse command-line options. We do this after qt in order to show an error if there are problems parsing these
// Command-line options take precedence:
node->setupServerArgs();
SetupUIArgs();
std::string error;
if (!node->parseParameters(argc, argv, error)) {
QMessageBox::critical(0, QObject::tr(PACKAGE_NAME),
QObject::tr("Error parsing command line arguments: %1.").arg(QString::fromStdString(error)));
return EXIT_FAILURE;
}
// Now that the QApplication is setup and we have parsed our parameters, we can set the platform style
app.setupPlatformStyle();
/// 3. Application identification
// must be set before OptionsModel is initialized or translations are loaded,
// as it is used to locate QSettings
@ -644,11 +659,9 @@ int main(int argc, char *argv[])
QObject::tr("Error: Specified data directory \"%1\" does not exist.").arg(QString::fromStdString(gArgs.GetArg("-datadir", ""))));
return EXIT_FAILURE;
}
try {
node->readConfigFiles();
} catch (const std::exception& e) {
if (!node->readConfigFiles(error)) {
QMessageBox::critical(0, QObject::tr(PACKAGE_NAME),
QObject::tr("Error: Cannot parse configuration file: %1. Only use key=value syntax.").arg(e.what()));
QObject::tr("Error: Cannot parse configuration file: %1.").arg(QString::fromStdString(error)));
return EXIT_FAILURE;
}

View file

@ -27,11 +27,21 @@ static void ResetArgs(const std::string& strArg)
for (std::string& s : vecArg)
vecChar.push_back(s.c_str());
gArgs.ParseParameters(vecChar.size(), vecChar.data());
std::string error;
gArgs.ParseParameters(vecChar.size(), vecChar.data(), error);
}
static void SetupArgs(const std::vector<std::string>& args)
{
gArgs.ClearArgs();
for (const std::string& arg : args) {
gArgs.AddArg(arg, "", false, OptionsCategory::OPTIONS);
}
}
BOOST_AUTO_TEST_CASE(boolarg)
{
SetupArgs({"-foo"});
ResetArgs("-foo");
BOOST_CHECK(gArgs.GetBoolArg("-foo", false));
BOOST_CHECK(gArgs.GetBoolArg("-foo", true));
@ -84,6 +94,7 @@ BOOST_AUTO_TEST_CASE(boolarg)
BOOST_AUTO_TEST_CASE(stringarg)
{
SetupArgs({"-foo", "-bar"});
ResetArgs("");
BOOST_CHECK_EQUAL(gArgs.GetArg("-foo", ""), "");
BOOST_CHECK_EQUAL(gArgs.GetArg("-foo", "eleven"), "eleven");
@ -108,6 +119,7 @@ BOOST_AUTO_TEST_CASE(stringarg)
BOOST_AUTO_TEST_CASE(intarg)
{
SetupArgs({"-foo", "-bar"});
ResetArgs("");
BOOST_CHECK_EQUAL(gArgs.GetArg("-foo", 11), 11);
BOOST_CHECK_EQUAL(gArgs.GetArg("-foo", 0), 0);
@ -127,6 +139,7 @@ BOOST_AUTO_TEST_CASE(intarg)
BOOST_AUTO_TEST_CASE(doubledash)
{
SetupArgs({"-foo", "-bar"});
ResetArgs("--foo");
BOOST_CHECK_EQUAL(gArgs.GetBoolArg("-foo", false), true);
@ -137,6 +150,7 @@ BOOST_AUTO_TEST_CASE(doubledash)
BOOST_AUTO_TEST_CASE(boolargno)
{
SetupArgs({"-foo", "-bar"});
ResetArgs("-nofoo");
BOOST_CHECK(!gArgs.GetBoolArg("-foo", true));
BOOST_CHECK(!gArgs.GetBoolArg("-foo", false));

View file

@ -186,27 +186,37 @@ struct TestArgsManager : public ArgsManager
LOCK(cs_args);
m_config_args.clear();
}
ReadConfigStream(streamConfig);
std::string error;
ReadConfigStream(streamConfig, error);
}
void SetNetworkOnlyArg(const std::string arg)
{
LOCK(cs_args);
m_network_only_args.insert(arg);
}
void SetupArgs(int argv, const char* args[])
{
for (int i = 0; i < argv; ++i) {
AddArg(args[i], "", false, OptionsCategory::OPTIONS);
}
}
};
BOOST_AUTO_TEST_CASE(util_ParseParameters)
{
TestArgsManager testArgs;
const char* avail_args[] = {"-a", "-b", "-ccc", "-d"};
const char *argv_test[] = {"-ignored", "-a", "-b", "-ccc=argument", "-ccc=multiple", "f", "-d=e"};
testArgs.ParseParameters(0, (char**)argv_test);
std::string error;
testArgs.SetupArgs(4, avail_args);
testArgs.ParseParameters(0, (char**)argv_test, error);
BOOST_CHECK(testArgs.GetOverrideArgs().empty() && testArgs.GetConfigArgs().empty());
testArgs.ParseParameters(1, (char**)argv_test);
testArgs.ParseParameters(1, (char**)argv_test, error);
BOOST_CHECK(testArgs.GetOverrideArgs().empty() && testArgs.GetConfigArgs().empty());
testArgs.ParseParameters(7, (char**)argv_test);
testArgs.ParseParameters(7, (char**)argv_test, error);
// expectation: -ignored is ignored (program name argument),
// -a, -b and -ccc end up in map, -d ignored because it is after
// a non-option argument (non-GNU option parsing)
@ -227,9 +237,12 @@ BOOST_AUTO_TEST_CASE(util_ParseParameters)
BOOST_AUTO_TEST_CASE(util_GetBoolArg)
{
TestArgsManager testArgs;
const char* avail_args[] = {"-a", "-b", "-c", "-d", "-e", "-f"};
const char *argv_test[] = {
"ignored", "-a", "-nob", "-c=0", "-d=1", "-e=false", "-f=true"};
testArgs.ParseParameters(7, (char**)argv_test);
std::string error;
testArgs.SetupArgs(6, avail_args);
testArgs.ParseParameters(7, (char**)argv_test, error);
// Each letter should be set.
for (char opt : "abcdef")
@ -261,8 +274,11 @@ BOOST_AUTO_TEST_CASE(util_GetBoolArgEdgeCases)
TestArgsManager testArgs;
// Params test
const char* avail_args[] = {"-foo", "-bar"};
const char *argv_test[] = {"ignored", "-nofoo", "-foo", "-nobar=0"};
testArgs.ParseParameters(4, (char**)argv_test);
testArgs.SetupArgs(2, avail_args);
std::string error;
testArgs.ParseParameters(4, (char**)argv_test, error);
// This was passed twice, second one overrides the negative setting.
BOOST_CHECK(!testArgs.IsArgNegated("-foo"));
@ -274,7 +290,7 @@ BOOST_AUTO_TEST_CASE(util_GetBoolArgEdgeCases)
// Config test
const char *conf_test = "nofoo=1\nfoo=1\nnobar=0\n";
testArgs.ParseParameters(1, (char**)argv_test);
testArgs.ParseParameters(1, (char**)argv_test, error);
testArgs.ReadConfigString(conf_test);
// This was passed twice, second one overrides the negative setting,
@ -289,7 +305,7 @@ BOOST_AUTO_TEST_CASE(util_GetBoolArgEdgeCases)
// Combined test
const char *combo_test_args[] = {"ignored", "-nofoo", "-bar"};
const char *combo_test_conf = "foo=1\nnobar=1\n";
testArgs.ParseParameters(3, (char**)combo_test_args);
testArgs.ParseParameters(3, (char**)combo_test_args, error);
testArgs.ReadConfigString(combo_test_conf);
// Command line overrides, but doesn't erase old setting
@ -329,6 +345,8 @@ BOOST_AUTO_TEST_CASE(util_ReadConfigStream)
"iii=2\n";
TestArgsManager test_args;
const char* avail_args[] = {"-a", "-b", "-ccc", "-d", "-e", "-fff", "-ggg", "-h", "-i", "-iii"};
test_args.SetupArgs(10, avail_args);
test_args.ReadConfigString(str_config);
// expectation: a, b, ccc, d, fff, ggg, h, i end up in map
@ -526,6 +544,8 @@ BOOST_AUTO_TEST_CASE(util_GetArg)
BOOST_AUTO_TEST_CASE(util_GetChainName)
{
TestArgsManager test_args;
const char* avail_args[] = {"-testnet", "-regtest"};
test_args.SetupArgs(2, avail_args);
const char* argv_testnet[] = {"cmd", "-testnet"};
const char* argv_regtest[] = {"cmd", "-regtest"};
@ -535,39 +555,40 @@ BOOST_AUTO_TEST_CASE(util_GetChainName)
// equivalent to "-testnet"
// regtest in testnet section is ignored
const char* testnetconf = "testnet=1\nregtest=0\n[test]\nregtest=1";
std::string error;
test_args.ParseParameters(0, (char**)argv_testnet);
test_args.ParseParameters(0, (char**)argv_testnet, error);
BOOST_CHECK_EQUAL(test_args.GetChainName(), "main");
test_args.ParseParameters(2, (char**)argv_testnet);
test_args.ParseParameters(2, (char**)argv_testnet, error);
BOOST_CHECK_EQUAL(test_args.GetChainName(), "test");
test_args.ParseParameters(2, (char**)argv_regtest);
test_args.ParseParameters(2, (char**)argv_regtest, error);
BOOST_CHECK_EQUAL(test_args.GetChainName(), "regtest");
test_args.ParseParameters(3, (char**)argv_test_no_reg);
test_args.ParseParameters(3, (char**)argv_test_no_reg, error);
BOOST_CHECK_EQUAL(test_args.GetChainName(), "test");
test_args.ParseParameters(3, (char**)argv_both);
test_args.ParseParameters(3, (char**)argv_both, error);
BOOST_CHECK_THROW(test_args.GetChainName(), std::runtime_error);
test_args.ParseParameters(0, (char**)argv_testnet);
test_args.ParseParameters(0, (char**)argv_testnet, error);
test_args.ReadConfigString(testnetconf);
BOOST_CHECK_EQUAL(test_args.GetChainName(), "test");
test_args.ParseParameters(2, (char**)argv_testnet);
test_args.ParseParameters(2, (char**)argv_testnet, error);
test_args.ReadConfigString(testnetconf);
BOOST_CHECK_EQUAL(test_args.GetChainName(), "test");
test_args.ParseParameters(2, (char**)argv_regtest);
test_args.ParseParameters(2, (char**)argv_regtest, error);
test_args.ReadConfigString(testnetconf);
BOOST_CHECK_THROW(test_args.GetChainName(), std::runtime_error);
test_args.ParseParameters(3, (char**)argv_test_no_reg);
test_args.ParseParameters(3, (char**)argv_test_no_reg, error);
test_args.ReadConfigString(testnetconf);
BOOST_CHECK_EQUAL(test_args.GetChainName(), "test");
test_args.ParseParameters(3, (char**)argv_both);
test_args.ParseParameters(3, (char**)argv_both, error);
test_args.ReadConfigString(testnetconf);
BOOST_CHECK_THROW(test_args.GetChainName(), std::runtime_error);
@ -575,23 +596,23 @@ BOOST_AUTO_TEST_CASE(util_GetChainName)
// [test] regtest=1 potentially relevant) doesn't break things
test_args.SelectConfigNetwork("test");
test_args.ParseParameters(0, (char**)argv_testnet);
test_args.ParseParameters(0, (char**)argv_testnet, error);
test_args.ReadConfigString(testnetconf);
BOOST_CHECK_EQUAL(test_args.GetChainName(), "test");
test_args.ParseParameters(2, (char**)argv_testnet);
test_args.ParseParameters(2, (char**)argv_testnet, error);
test_args.ReadConfigString(testnetconf);
BOOST_CHECK_EQUAL(test_args.GetChainName(), "test");
test_args.ParseParameters(2, (char**)argv_regtest);
test_args.ParseParameters(2, (char**)argv_regtest, error);
test_args.ReadConfigString(testnetconf);
BOOST_CHECK_THROW(test_args.GetChainName(), std::runtime_error);
test_args.ParseParameters(2, (char**)argv_test_no_reg);
test_args.ParseParameters(2, (char**)argv_test_no_reg, error);
test_args.ReadConfigString(testnetconf);
BOOST_CHECK_EQUAL(test_args.GetChainName(), "test");
test_args.ParseParameters(3, (char**)argv_both);
test_args.ParseParameters(3, (char**)argv_both, error);
test_args.ReadConfigString(testnetconf);
BOOST_CHECK_THROW(test_args.GetChainName(), std::runtime_error);
}

View file

@ -412,7 +412,7 @@ void ArgsManager::SelectConfigNetwork(const std::string& network)
m_network = network;
}
void ArgsManager::ParseParameters(int argc, const char* const argv[])
bool ArgsManager::ParseParameters(int argc, const char* const argv[], std::string& error)
{
LOCK(cs_args);
m_override_args.clear();
@ -444,6 +444,14 @@ void ArgsManager::ParseParameters(int argc, const char* const argv[])
} else {
m_override_args[key].push_back(val);
}
// Check that the arg is known
if (!(IsSwitchChar(key[0]) && key.size() == 1)) {
if (!IsArgKnown(key, error)) {
error = strprintf("Invalid parameter %s", key.c_str());
return false;
}
}
}
// we do not allow -includeconf from command line, so we clear it here
@ -456,6 +464,23 @@ void ArgsManager::ParseParameters(int argc, const char* const argv[])
m_override_args.erase(it);
}
}
return true;
}
bool ArgsManager::IsArgKnown(const std::string& key, std::string& error)
{
size_t option_index = key.find('.');
std::string arg_no_net;
if (option_index == std::string::npos) {
arg_no_net = key;
} else {
arg_no_net = std::string("-") + key.substr(option_index + 1, std::string::npos);
}
for (const auto& arg_map : m_available_args) {
if (arg_map.second.count(arg_no_net)) return true;
}
return false;
}
std::vector<std::string> ArgsManager::GetArgs(const std::string& strArg) const
@ -779,7 +804,7 @@ fs::path GetConfigFile(const std::string& confPath)
return AbsPathForConfigVal(fs::path(confPath), false);
}
void ArgsManager::ReadConfigStream(std::istream& stream)
bool ArgsManager::ReadConfigStream(std::istream& stream, std::string& error, bool ignore_invalid_keys)
{
LOCK(cs_args);
@ -790,15 +815,23 @@ void ArgsManager::ReadConfigStream(std::istream& stream)
{
std::string strKey = std::string("-") + it->string_key;
std::string strValue = it->value[0];
if (InterpretNegatedOption(strKey, strValue)) {
m_config_args[strKey].clear();
} else {
m_config_args[strKey].push_back(strValue);
}
// Check that the arg is known
if (!IsArgKnown(strKey, error) && !ignore_invalid_keys) {
error = strprintf("Invalid configuration value %s", it->string_key.c_str());
return false;
}
}
return true;
}
void ArgsManager::ReadConfigFiles()
bool ArgsManager::ReadConfigFiles(std::string& error, bool ignore_invalid_keys)
{
{
LOCK(cs_args);
@ -810,7 +843,9 @@ void ArgsManager::ReadConfigFiles()
// ok to not have a config file
if (stream.good()) {
ReadConfigStream(stream);
if (!ReadConfigStream(stream, error, ignore_invalid_keys)) {
return false;
}
// 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
if (m_override_args.count("-includeconf") == 0) {
@ -833,7 +868,9 @@ void ArgsManager::ReadConfigFiles()
for (const std::string& to_include : includeconf) {
fs::ifstream include_config(GetConfigFile(to_include));
if (include_config.good()) {
ReadConfigStream(include_config);
if (!ReadConfigStream(include_config, error, ignore_invalid_keys)) {
return false;
}
LogPrintf("Included configuration file %s\n", to_include.c_str());
} else {
fprintf(stderr, "Failed to include configuration file %s\n", to_include.c_str());
@ -855,8 +892,10 @@ void ArgsManager::ReadConfigFiles()
// If datadir is changed in .conf file:
ClearDatadirCache();
if (!fs::is_directory(GetDataDir(false))) {
throw std::runtime_error(strprintf("specified data directory \"%s\" does not exist.", gArgs.GetArg("-datadir", "").c_str()));
error = strprintf("specified data directory \"%s\" does not exist.", gArgs.GetArg("-datadir", "").c_str());
return false;
}
return true;
}
std::string ArgsManager::GetChainName() const

View file

@ -118,8 +118,7 @@ inline bool IsSwitchChar(char c)
#endif
}
enum class OptionsCategory
{
enum class OptionsCategory {
OPTIONS,
CONNECTION,
WALLET,
@ -132,7 +131,9 @@ enum class OptionsCategory
RPC,
GUI,
COMMANDS,
REGISTER_COMMANDS
REGISTER_COMMANDS,
HIDDEN // Always the last option to avoid printing these in the help
};
class ArgsManager
@ -156,7 +157,7 @@ protected:
std::set<std::string> m_network_only_args;
std::map<OptionsCategory, std::map<std::string, Arg>> m_available_args;
void ReadConfigStream(std::istream& stream);
bool ReadConfigStream(std::istream& stream, std::string& error, bool ignore_invalid_keys = false);
public:
ArgsManager();
@ -166,8 +167,8 @@ public:
*/
void SelectConfigNetwork(const std::string& network);
void ParseParameters(int argc, const char*const argv[]);
void ReadConfigFiles();
bool ParseParameters(int argc, const char* const argv[], std::string& error);
bool ReadConfigFiles(std::string& error, bool ignore_invalid_keys = false);
/**
* Log warnings for options in m_section_only_args when
@ -262,10 +263,20 @@ public:
*/
void AddArg(const std::string& name, const std::string& help, const bool debug_only, const OptionsCategory& cat);
/**
* Clear available arguments
*/
void ClearArgs() { m_available_args.clear(); }
/**
* Get the help string
*/
std::string GetHelpMessage();
/**
* Check whether we know of this arg
*/
bool IsArgKnown(const std::string& key, std::string& error);
};
extern ArgsManager gArgs;