aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/bitcoin-wallet.cpp30
-rw-r--r--src/test/fuzz/system.cpp2
-rw-r--r--src/util/system.cpp53
-rw-r--r--src/util/system.h22
-rwxr-xr-xtest/functional/tool_wallet.py6
5 files changed, 89 insertions, 24 deletions
diff --git a/src/bitcoin-wallet.cpp b/src/bitcoin-wallet.cpp
index 0b55b587fd..b84d909b07 100644
--- a/src/bitcoin-wallet.cpp
+++ b/src/bitcoin-wallet.cpp
@@ -33,11 +33,11 @@ static void SetupWalletToolArgs(ArgsManager& argsman)
argsman.AddArg("-format=<format>", "The format of the wallet file to create. Either \"bdb\" or \"sqlite\". Only used with 'createfromdump'", ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS);
argsman.AddArg("-printtoconsole", "Send trace/debug info to console (default: 1 when no -debug is true, 0 otherwise).", ArgsManager::ALLOW_ANY, OptionsCategory::DEBUG_TEST);
- argsman.AddArg("info", "Get wallet info", ArgsManager::ALLOW_ANY, OptionsCategory::COMMANDS);
- argsman.AddArg("create", "Create new wallet file", ArgsManager::ALLOW_ANY, OptionsCategory::COMMANDS);
- argsman.AddArg("salvage", "Attempt to recover private keys from a corrupt wallet. Warning: 'salvage' is experimental.", ArgsManager::ALLOW_ANY, OptionsCategory::COMMANDS);
- argsman.AddArg("dump", "Print out all of the wallet key-value records", ArgsManager::ALLOW_ANY, OptionsCategory::COMMANDS);
- argsman.AddArg("createfromdump", "Create new wallet file from dumped records", ArgsManager::ALLOW_ANY, OptionsCategory::COMMANDS);
+ argsman.AddCommand("info", "Get wallet info", OptionsCategory::COMMANDS);
+ argsman.AddCommand("create", "Create new wallet file", OptionsCategory::COMMANDS);
+ argsman.AddCommand("salvage", "Attempt to recover private keys from a corrupt wallet. Warning: 'salvage' is experimental.", OptionsCategory::COMMANDS);
+ argsman.AddCommand("dump", "Print out all of the wallet key-value records", OptionsCategory::COMMANDS);
+ argsman.AddCommand("createfromdump", "Create new wallet file from dumped records", OptionsCategory::COMMANDS);
}
static bool WalletAppInit(ArgsManager& args, int argc, char* argv[])
@@ -95,25 +95,19 @@ int main(int argc, char* argv[])
return EXIT_FAILURE;
}
- std::string method {};
- for(int i = 1; i < argc; ++i) {
- if (!IsSwitchChar(argv[i][0])) {
- if (!method.empty()) {
- tfm::format(std::cerr, "Error: two methods provided (%s and %s). Only one method should be provided.\n", method, argv[i]);
- return EXIT_FAILURE;
- }
- method = argv[i];
- }
- }
-
- if (method.empty()) {
+ const auto command = args.GetCommand();
+ if (!command) {
tfm::format(std::cerr, "No method provided. Run `bitcoin-wallet -help` for valid methods.\n");
return EXIT_FAILURE;
}
+ if (command->args.size() != 0) {
+ tfm::format(std::cerr, "Error: Additional arguments provided (%s). Methods do not take arguments. Please refer to `-help`.\n", Join(command->args, ", "));
+ return EXIT_FAILURE;
+ }
ECCVerifyHandle globalVerifyHandle;
ECC_Start();
- if (!WalletTool::ExecuteWalletToolFunc(args, method)) {
+ if (!WalletTool::ExecuteWalletToolFunc(args, command->command)) {
return EXIT_FAILURE;
}
ECC_Stop();
diff --git a/src/test/fuzz/system.cpp b/src/test/fuzz/system.cpp
index 47b38b6d23..3621702e45 100644
--- a/src/test/fuzz/system.cpp
+++ b/src/test/fuzz/system.cpp
@@ -54,7 +54,7 @@ FUZZ_TARGET(system)
if (args_manager.GetArgFlags(argument_name) != nullopt) {
return;
}
- args_manager.AddArg(argument_name, fuzzed_data_provider.ConsumeRandomLengthString(16), fuzzed_data_provider.ConsumeIntegral<unsigned int>(), options_category);
+ args_manager.AddArg(argument_name, fuzzed_data_provider.ConsumeRandomLengthString(16), fuzzed_data_provider.ConsumeIntegral<unsigned int>() & ~ArgsManager::COMMAND, options_category);
},
[&] {
// Avoid hitting:
diff --git a/src/util/system.cpp b/src/util/system.cpp
index d1fb921642..9fe9d67411 100644
--- a/src/util/system.cpp
+++ b/src/util/system.cpp
@@ -3,7 +3,6 @@
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
-#include <sync.h>
#include <util/system.h>
#ifdef HAVE_BOOST_PROCESS
@@ -11,6 +10,8 @@
#endif // HAVE_BOOST_PROCESS
#include <chainparamsbase.h>
+#include <sync.h>
+#include <util/check.h>
#include <util/strencodings.h>
#include <util/string.h>
#include <util/translation.h>
@@ -310,8 +311,22 @@ bool ArgsManager::ParseParameters(int argc, const char* const argv[], std::strin
key[0] = '-';
#endif
- if (key[0] != '-')
+ if (key[0] != '-') {
+ if (!m_accept_any_command && m_command.empty()) {
+ // The first non-dash arg is a registered command
+ Optional<unsigned int> flags = GetArgFlags(key);
+ if (!flags || !(*flags & ArgsManager::COMMAND)) {
+ error = strprintf("Invalid command '%s'", argv[i]);
+ return false;
+ }
+ }
+ m_command.push_back(key);
+ while (++i < argc) {
+ // The remaining args are command args
+ m_command.push_back(argv[i]);
+ }
break;
+ }
// Transform --foo to -foo
if (key.length() > 1 && key[1] == '-')
@@ -359,6 +374,26 @@ Optional<unsigned int> ArgsManager::GetArgFlags(const std::string& name) const
return nullopt;
}
+std::optional<const ArgsManager::Command> ArgsManager::GetCommand() const
+{
+ Command ret;
+ LOCK(cs_args);
+ auto it = m_command.begin();
+ if (it == m_command.end()) {
+ // No command was passed
+ return std::nullopt;
+ }
+ if (!m_accept_any_command) {
+ // The registered command
+ ret.command = *(it++);
+ }
+ while (it != m_command.end()) {
+ // The unregistered command and args (if any)
+ ret.args.push_back(*(it++));
+ }
+ return ret;
+}
+
std::vector<std::string> ArgsManager::GetArgs(const std::string& strArg) const
{
std::vector<std::string> result;
@@ -504,8 +539,22 @@ void ArgsManager::ForceSetArg(const std::string& strArg, const std::string& strV
m_settings.forced_settings[SettingName(strArg)] = strValue;
}
+void ArgsManager::AddCommand(const std::string& cmd, const std::string& help, const OptionsCategory& cat)
+{
+ Assert(cmd.find('=') == std::string::npos);
+ Assert(cmd.at(0) != '-');
+
+ LOCK(cs_args);
+ m_accept_any_command = false; // latch to false
+ std::map<std::string, Arg>& arg_map = m_available_args[cat];
+ auto ret = arg_map.emplace(cmd, Arg{"", help, ArgsManager::COMMAND});
+ Assert(ret.second); // Fail on duplicate commands
+}
+
void ArgsManager::AddArg(const std::string& name, const std::string& help, unsigned int flags, const OptionsCategory& cat)
{
+ Assert((flags & ArgsManager::COMMAND) == 0); // use AddCommand
+
// Split arg name from its help param
size_t eq_index = name.find('=');
if (eq_index == std::string::npos) {
diff --git a/src/util/system.h b/src/util/system.h
index 010fc5b49f..e8e1fab333 100644
--- a/src/util/system.h
+++ b/src/util/system.h
@@ -183,6 +183,7 @@ public:
NETWORK_ONLY = 0x200,
// This argument's value is sensitive (such as a password).
SENSITIVE = 0x400,
+ COMMAND = 0x800,
};
protected:
@@ -195,9 +196,11 @@ protected:
mutable RecursiveMutex cs_args;
util::Settings m_settings GUARDED_BY(cs_args);
+ std::vector<std::string> m_command GUARDED_BY(cs_args);
std::string m_network GUARDED_BY(cs_args);
std::set<std::string> m_network_only_args GUARDED_BY(cs_args);
std::map<OptionsCategory, std::map<std::string, Arg>> m_available_args GUARDED_BY(cs_args);
+ bool m_accept_any_command GUARDED_BY(cs_args){true};
std::list<SectionInfo> m_config_sections GUARDED_BY(cs_args);
[[nodiscard]] bool ReadConfigStream(std::istream& stream, const std::string& filepath, std::string& error, bool ignore_invalid_keys = false);
@@ -248,6 +251,20 @@ public:
*/
const std::list<SectionInfo> GetUnrecognizedSections() const;
+ struct Command {
+ /** The command (if one has been registered with AddCommand), or empty */
+ std::string command;
+ /**
+ * If command is non-empty: Any args that followed it
+ * If command is empty: The unregistered command and any args that followed it
+ */
+ std::vector<std::string> args;
+ };
+ /**
+ * Get the command and command args (returns std::nullopt if no command provided)
+ */
+ std::optional<const Command> GetCommand() const;
+
/**
* Return a vector of strings of the given argument
*
@@ -334,6 +351,11 @@ public:
void AddArg(const std::string& name, const std::string& help, unsigned int flags, const OptionsCategory& cat);
/**
+ * Add subcommand
+ */
+ void AddCommand(const std::string& cmd, const std::string& help, const OptionsCategory& cat);
+
+ /**
* Add many hidden arguments
*/
void AddHiddenArgs(const std::vector<std::string>& args);
diff --git a/test/functional/tool_wallet.py b/test/functional/tool_wallet.py
index cbc6feaa0e..cca984b292 100755
--- a/test/functional/tool_wallet.py
+++ b/test/functional/tool_wallet.py
@@ -183,10 +183,10 @@ class ToolWalletTest(BitcoinTestFramework):
def test_invalid_tool_commands_and_args(self):
self.log.info('Testing that various invalid commands raise with specific error messages')
- self.assert_raises_tool_error('Invalid command: foo', 'foo')
+ self.assert_raises_tool_error("Error parsing command line arguments: Invalid command 'foo'", 'foo')
# `bitcoin-wallet help` raises an error. Use `bitcoin-wallet -help`.
- self.assert_raises_tool_error('Invalid command: help', 'help')
- self.assert_raises_tool_error('Error: two methods provided (info and create). Only one method should be provided.', 'info', 'create')
+ self.assert_raises_tool_error("Error parsing command line arguments: Invalid command 'help'", 'help')
+ self.assert_raises_tool_error('Error: Additional arguments provided (create). Methods do not take arguments. Please refer to `-help`.', 'info', 'create')
self.assert_raises_tool_error('Error parsing command line arguments: Invalid parameter -foo', '-foo')
self.assert_raises_tool_error('No method provided. Run `bitcoin-wallet -help` for valid methods.')
self.assert_raises_tool_error('Wallet name must be provided when creating a new wallet.', 'create')