util: Remove brittle and confusing sp::Popen(std::string)

This commit is contained in:
MarcoFalke committed 2026-02-17 12:55:26 +01:00
1 parent c8c9c1e617
commit fa626bd143
6 files changed
+38 -48

No files matched your search

+5 -4
View File
@@ -8,12 +8,13 @@
#include <tinyformat.h> #include <tinyformat.h>
#include <univalue.h> #include <univalue.h>
#include <util/string.h>
#ifdef ENABLE_EXTERNAL_SIGNER #ifdef ENABLE_EXTERNAL_SIGNER
#include <util/subprocess.h> #include <util/subprocess.h>
#endif // ENABLE_EXTERNAL_SIGNER #endif // ENABLE_EXTERNAL_SIGNER
UniValue RunCommandParseJSON(const std::string& str_command, const std::string& str_std_in) UniValue RunCommandParseJSON(const std::vector<std::string>& cmd_args, const std::string& str_std_in)
{ {
#ifdef ENABLE_EXTERNAL_SIGNER #ifdef ENABLE_EXTERNAL_SIGNER
namespace sp = subprocess; namespace sp = subprocess;
@@ -22,9 +23,9 @@ UniValue RunCommandParseJSON(const std::string& str_command, const std::string&
std::istringstream stdout_stream; std::istringstream stdout_stream;
std::istringstream stderr_stream; std::istringstream stderr_stream;
if (str_command.empty()) return UniValue::VNULL; if (cmd_args.empty()) return UniValue::VNULL;
auto c = sp::Popen(str_command, sp::input{sp::PIPE}, sp::output{sp::PIPE}, sp::error{sp::PIPE}); auto c = sp::Popen(cmd_args, sp::input{sp::PIPE}, sp::output{sp::PIPE}, sp::error{sp::PIPE});
if (!str_std_in.empty()) { if (!str_std_in.empty()) {
c.send(str_std_in); c.send(str_std_in);
} }
@@ -38,7 +39,7 @@ UniValue RunCommandParseJSON(const std::string& str_command, const std::string&
std::getline(stderr_stream, error); std::getline(stderr_stream, error);
const int n_error = c.retcode(); const int n_error = c.retcode();
if (n_error) throw std::runtime_error(strprintf("RunCommandParseJSON error: process(%s) returned %d: %s\n", str_command, n_error, error)); if (n_error) throw std::runtime_error(strprintf("RunCommandParseJSON error: process(%s) returned %d: %s\n", util::Join(cmd_args, " "), n_error, error));
if (!result_json.read(result)) throw std::runtime_error("Unable to parse JSON: " + result); if (!result_json.read(result)) throw std::runtime_error("Unable to parse JSON: " + result);
return result_json; return result_json;
+3 -2
View File
@@ -6,16 +6,17 @@
#define BITCOIN_COMMON_RUN_COMMAND_H #define BITCOIN_COMMON_RUN_COMMAND_H
#include <string> #include <string>
#include <vector>
class UniValue; class UniValue;
/** /**
* Execute a command which returns JSON, and parse the result. * Execute a command which returns JSON, and parse the result.
* *
* @param str_command The command to execute, including any arguments * @param cmd_args The command and arguments
* @param str_std_in string to pass to stdin * @param str_std_in string to pass to stdin
* @return parsed JSON * @return parsed JSON
*/ */
UniValue RunCommandParseJSON(const std::string& str_command, const std::string& str_std_in=""); UniValue RunCommandParseJSON(const std::vector<std::string>& cmd_args, const std::string& str_std_in = "");
#endif // BITCOIN_COMMON_RUN_COMMAND_H #endif // BITCOIN_COMMON_RUN_COMMAND_H
+11 -8
View File
@@ -9,24 +9,27 @@
#include <core_io.h> #include <core_io.h>
#include <psbt.h> #include <psbt.h>
#include <util/strencodings.h> #include <util/strencodings.h>
#include <util/subprocess.h>
#include <algorithm> #include <algorithm>
#include <stdexcept> #include <stdexcept>
#include <string> #include <string>
#include <vector> #include <vector>
ExternalSigner::ExternalSigner(std::string command, std::string chain, std::string fingerprint, std::string name) ExternalSigner::ExternalSigner(std::vector<std::string> command, std::string chain, std::string fingerprint, std::string name)
: m_command{std::move(command)}, m_chain{std::move(chain)}, m_fingerprint{std::move(fingerprint)}, m_name{std::move(name)} {} : m_command{std::move(command)}, m_chain{std::move(chain)}, m_fingerprint{std::move(fingerprint)}, m_name{std::move(name)} {}
std::string ExternalSigner::NetworkArg() const std::vector<std::string> ExternalSigner::NetworkArg() const
{ {
return " --chain " + m_chain; return {"--chain", m_chain};
} }
bool ExternalSigner::Enumerate(const std::string& command, std::vector<ExternalSigner>& signers, const std::string& chain) bool ExternalSigner::Enumerate(const std::string& command, std::vector<ExternalSigner>& signers, const std::string& chain)
{ {
// Call <command> enumerate // Call <command> enumerate
const UniValue result = RunCommandParseJSON(command + " enumerate"); std::vector<std::string> cmd_args = Cat(subprocess::util::split(command), {"enumerate"});
const UniValue result = RunCommandParseJSON(cmd_args, "");
if (!result.isArray()) { if (!result.isArray()) {
throw std::runtime_error(strprintf("'%s' received invalid response, expected array of signers", command)); throw std::runtime_error(strprintf("'%s' received invalid response, expected array of signers", command));
} }
@@ -56,19 +59,19 @@ bool ExternalSigner::Enumerate(const std::string& command, std::vector<ExternalS
if (model_field.isStr() && model_field.getValStr() != "") { if (model_field.isStr() && model_field.getValStr() != "") {
name += model_field.getValStr(); name += model_field.getValStr();
} }
signers.emplace_back(command, chain, fingerprintStr, name); signers.emplace_back(subprocess::util::split(command), chain, fingerprintStr, name);
} }
return true; return true;
} }
UniValue ExternalSigner::DisplayAddress(const std::string& descriptor) const UniValue ExternalSigner::DisplayAddress(const std::string& descriptor) const
{ {
return RunCommandParseJSON(m_command + " --fingerprint " + m_fingerprint + NetworkArg() + " displayaddress --desc " + descriptor); return RunCommandParseJSON(Cat(m_command, Cat(Cat({"--fingerprint", m_fingerprint}, NetworkArg()), {"displayaddress", "--desc", descriptor})), "");
} }
UniValue ExternalSigner::GetDescriptors(const int account) UniValue ExternalSigner::GetDescriptors(const int account)
{ {
return RunCommandParseJSON(m_command + " --fingerprint " + m_fingerprint + NetworkArg() + " getdescriptors --account " + strprintf("%d", account)); return RunCommandParseJSON(Cat(m_command, Cat(Cat({"--fingerprint", m_fingerprint}, NetworkArg()), {"getdescriptors", "--account", strprintf("%d", account)})), "");
} }
bool ExternalSigner::SignTransaction(PartiallySignedTransaction& psbtx, std::string& error) bool ExternalSigner::SignTransaction(PartiallySignedTransaction& psbtx, std::string& error)
@@ -94,7 +97,7 @@ bool ExternalSigner::SignTransaction(PartiallySignedTransaction& psbtx, std::str
return false; return false;
} }
const std::string command = m_command + " --stdin --fingerprint " + m_fingerprint + NetworkArg(); const std::vector<std::string> command = Cat(m_command, Cat({"--stdin", "--fingerprint", m_fingerprint}, NetworkArg()));
const std::string stdinStr = "signtx " + EncodeBase64(ssTx.str()); const std::string stdinStr = "signtx " + EncodeBase64(ssTx.str());
const UniValue signer_result = RunCommandParseJSON(command, stdinStr); const UniValue signer_result = RunCommandParseJSON(command, stdinStr);
+3 -3
View File
@@ -19,19 +19,19 @@ class ExternalSigner
{ {
private: private:
//! The command which handles interaction with the external signer. //! The command which handles interaction with the external signer.
std::string m_command; std::vector<std::string> m_command;
//! Bitcoin mainnet, testnet, etc //! Bitcoin mainnet, testnet, etc
std::string m_chain; std::string m_chain;
std::string NetworkArg() const; std::vector<std::string> NetworkArg() const;
public: public:
//! @param[in] command the command which handles interaction with the external signer //! @param[in] command the command which handles interaction with the external signer
//! @param[in] fingerprint master key fingerprint of the signer //! @param[in] fingerprint master key fingerprint of the signer
//! @param[in] chain "main", "test", "regtest" or "signet" //! @param[in] chain "main", "test", "regtest" or "signet"
//! @param[in] name device name //! @param[in] name device name
ExternalSigner(std::string command, std::string chain, std::string fingerprint, std::string name); ExternalSigner(std::vector<std::string> command, std::string chain, std::string fingerprint, std::string name);
//! Master key fingerprint of the signer //! Master key fingerprint of the signer
std::string m_fingerprint; std::string m_fingerprint;
+16 -14
View File
@@ -4,9 +4,11 @@
// //
#include <bitcoin-build-config.h> // IWYU pragma: keep #include <bitcoin-build-config.h> // IWYU pragma: keep
#include <test/util/setup_common.h>
#include <common/run_command.h> #include <common/run_command.h>
#include <test/util/setup_common.h>
#include <univalue.h> #include <univalue.h>
#include <util/string.h>
#ifdef ENABLE_EXTERNAL_SIGNER #ifdef ENABLE_EXTERNAL_SIGNER
#include <util/subprocess.h> #include <util/subprocess.h>
@@ -21,14 +23,14 @@ BOOST_FIXTURE_TEST_SUITE(system_tests, BasicTestingSetup)
BOOST_AUTO_TEST_CASE(run_command) BOOST_AUTO_TEST_CASE(run_command)
{ {
{ {
const UniValue result = RunCommandParseJSON(""); const UniValue result = RunCommandParseJSON({});
BOOST_CHECK(result.isNull()); BOOST_CHECK(result.isNull());
} }
{ {
#ifdef WIN32 #ifdef WIN32
const UniValue result = RunCommandParseJSON("cmd.exe /c echo {\"success\": true}"); const UniValue result = RunCommandParseJSON({"cmd.exe", "/c", "echo", "{\"success\":", "true}"}); // The command is intentionally split "incorrectly", to exactly preserve previous behavior. This is due to the cmd.exe internal echo quoting strings with spaces in it, unlike the normal 'echo' below.
#else #else
const UniValue result = RunCommandParseJSON("echo {\"success\": true}"); const UniValue result = RunCommandParseJSON({"echo", "{\"success\": true}"});
#endif #endif
BOOST_CHECK(result.isObject()); BOOST_CHECK(result.isObject());
const UniValue& success = result.find_value("success"); const UniValue& success = result.find_value("success");
@@ -42,32 +44,32 @@ BOOST_AUTO_TEST_CASE(run_command)
#else #else
const std::string expected{"execve failed: "}; const std::string expected{"execve failed: "};
#endif #endif
BOOST_CHECK_EXCEPTION(RunCommandParseJSON("invalid_command"), subprocess::CalledProcessError, HasReason(expected)); BOOST_CHECK_EXCEPTION(RunCommandParseJSON({"invalid_command"}), subprocess::CalledProcessError, HasReason(expected));
} }
{ {
// Return non-zero exit code, no output to stderr // Return non-zero exit code, no output to stderr
#ifdef WIN32 #ifdef WIN32
const std::string command{"cmd.exe /c exit 1"}; const std::vector<std::string> command = {"cmd.exe", "/c", "exit 1"};
#else #else
const std::string command{"false"}; const std::vector<std::string> command = {"false"};
#endif #endif
BOOST_CHECK_EXCEPTION(RunCommandParseJSON(command), std::runtime_error, [&](const std::runtime_error& e) { BOOST_CHECK_EXCEPTION(RunCommandParseJSON(command), std::runtime_error, [&](const std::runtime_error& e) {
const std::string what{e.what()}; const std::string what{e.what()};
BOOST_CHECK(what.find(strprintf("RunCommandParseJSON error: process(%s) returned 1: \n", command)) != std::string::npos); BOOST_CHECK(what.find(strprintf("RunCommandParseJSON error: process(%s) returned 1: \n", util::Join(command, " "))) != std::string::npos);
return true; return true;
}); });
} }
{ {
// Return non-zero exit code, with error message for stderr // Return non-zero exit code, with error message for stderr
#ifdef WIN32 #ifdef WIN32
const std::string command{"cmd.exe /c \"echo err 1>&2 && exit 1\""}; const std::vector<std::string> command = {"cmd.exe", "/c", "echo err 1>&2 && exit 1"};
#else #else
const std::string command{"sh -c 'echo err 1>&2 && false'"}; const std::vector<std::string> command = {"sh", "-c", "echo err 1>&2 && false"};
#endif #endif
const std::string expected{"err"}; const std::string expected{"err"};
BOOST_CHECK_EXCEPTION(RunCommandParseJSON(command), std::runtime_error, [&](const std::runtime_error& e) { BOOST_CHECK_EXCEPTION(RunCommandParseJSON(command), std::runtime_error, [&](const std::runtime_error& e) {
const std::string what(e.what()); const std::string what(e.what());
BOOST_CHECK(what.find(strprintf("RunCommandParseJSON error: process(%s) returned", command)) != std::string::npos); BOOST_CHECK(what.find(strprintf("RunCommandParseJSON error: process(%s) returned", util::Join(command, " "))) != std::string::npos);
BOOST_CHECK(what.find(expected) != std::string::npos); BOOST_CHECK(what.find(expected) != std::string::npos);
return true; return true;
}); });
@@ -75,16 +77,16 @@ BOOST_AUTO_TEST_CASE(run_command)
{ {
// Unable to parse JSON // Unable to parse JSON
#ifdef WIN32 #ifdef WIN32
const std::string command{"cmd.exe /c echo {"}; const std::vector<std::string> command = {"cmd.exe", "/c", "echo {"};
#else #else
const std::string command{"echo {"}; const std::vector<std::string> command = {"echo", "{"};
#endif #endif
BOOST_CHECK_EXCEPTION(RunCommandParseJSON(command), std::runtime_error, HasReason("Unable to parse JSON: {")); BOOST_CHECK_EXCEPTION(RunCommandParseJSON(command), std::runtime_error, HasReason("Unable to parse JSON: {"));
} }
#ifndef WIN32 #ifndef WIN32
{ {
// Test stdin // Test stdin
const UniValue result = RunCommandParseJSON("cat", "{\"success\": true}"); const UniValue result = RunCommandParseJSON({"cat"}, "{\"success\": true}");
BOOST_CHECK(result.isObject()); BOOST_CHECK(result.isObject());
const UniValue& success = result.find_value("success"); const UniValue& success = result.find_value("success");
BOOST_CHECK(!success.isNull()); BOOST_CHECK(!success.isNull());
-17
View File
@@ -920,8 +920,6 @@ private:
* API's provided by the class: * API's provided by the class:
* Popen({"cmd"}, output{..}, error{..}, ....) * Popen({"cmd"}, output{..}, error{..}, ....)
* Command provided as a sequence. * Command provided as a sequence.
* Popen("cmd arg1", output{..}, error{..}, ....)
* Command provided in a single string.
* wait() - Wait for the child to exit. * wait() - Wait for the child to exit.
* retcode() - The return code of the exited child. * retcode() - The return code of the exited child.
* send(...) - Send input to the input channel of the child. * send(...) - Send input to the input channel of the child.
@@ -934,19 +932,6 @@ public:
friend struct detail::ArgumentDeducer; friend struct detail::ArgumentDeducer;
friend class detail::Child; friend class detail::Child;
template <typename... Args>
Popen(const std::string& cmd_args, Args&& ...args):
args_(cmd_args)
{
vargs_ = util::split(cmd_args);
init_args(std::forward<Args>(args)...);
// Setup the communication channels of the Popen class
stream_.setup_comm_channels();
execute_process();
}
template <typename... Args> template <typename... Args>
Popen(std::initializer_list<const char*> cmd_args, Args&& ...args) Popen(std::initializer_list<const char*> cmd_args, Args&& ...args)
{ {
@@ -1028,8 +1013,6 @@ private:
std::string exe_name_; std::string exe_name_;
// Command in string format
std::string args_;
// Command provided as sequence // Command provided as sequence
std::vector<std::string> vargs_; std::vector<std::string> vargs_;
std::vector<char*> cargv_; std::vector<char*> cargv_;