Skip to content

Commit 0a9cfd1

Browse files
committed
Merge bitcoin/bitcoin#28981: Replace Boost.Process with cpp-subprocess
d5a7155 build: remove boost::process dependency for building external signer support (Sebastian Falbesoner) 70434b1 external_signer: replace boost::process with cpp-subprocess (Sebastian Falbesoner) cc8b987 Add `cpp-subprocess` header-only library (Hennadii Stepanov) Pull request description: Closes bitcoin/bitcoin#24907. This PR is based on **theStack**'s [work](bitcoin/bitcoin#24907 (comment)). The `subprocess.hpp` header has been sourced from the [upstream repo](https://github.com/arun11299/cpp-subprocess) with the only modification being the removal of convenience functions, which are not utilized in our codebase. Windows-related changes will be addressed in subsequent follow-ups. ACKs for top commit: achow101: reACK d5a7155 Sjors: re-tACK d5a7155 theStack: Light re-ACK d5a7155 fanquake: ACK d5a7155 - with the expectation that this code is going to be maintained as our own. Next PRs should: Tree-SHA512: d7fb6fecc3f5792496204190afb7d85b3e207b858fb1a75efe483c05260843b81b27d14b299323bb667c990e87a07197059afea3796cf218ed8b614086bd3611
2 parents e319569 + d5a7155 commit 0a9cfd1

File tree

8 files changed

+2017
-82
lines changed

8 files changed

+2017
-82
lines changed

configure.ac

Lines changed: 10 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -307,9 +307,9 @@ AC_ARG_ENABLE([werror],
307307
[enable_werror=no])
308308

309309
AC_ARG_ENABLE([external-signer],
310-
[AS_HELP_STRING([--enable-external-signer],[compile external signer support (default is auto, requires Boost::Process)])],
310+
[AS_HELP_STRING([--enable-external-signer],[compile external signer support (default is yes)])],
311311
[use_external_signer=$enableval],
312-
[use_external_signer=auto])
312+
[use_external_signer=yes])
313313

314314
AC_LANG_PUSH([C++])
315315

@@ -1414,56 +1414,14 @@ if test "$use_boost" = "yes"; then
14141414
fi
14151415
fi
14161416

1417-
if test "$use_external_signer" != "no"; then
1418-
AC_MSG_CHECKING([whether Boost.Process can be used])
1419-
TEMP_CXXFLAGS="$CXXFLAGS"
1420-
dnl Boost 1.78 requires the following workaround.
1421-
dnl See: https://github.com/boostorg/process/issues/235
1422-
CXXFLAGS="$CXXFLAGS -Wno-error=narrowing"
1423-
TEMP_CPPFLAGS="$CPPFLAGS"
1424-
CPPFLAGS="$CPPFLAGS $BOOST_CPPFLAGS"
1425-
TEMP_LDFLAGS="$LDFLAGS"
1426-
dnl Boost 1.73 and older require the following workaround.
1427-
LDFLAGS="$LDFLAGS $PTHREAD_CFLAGS"
1428-
AC_LINK_IFELSE([AC_LANG_PROGRAM([[
1429-
#define BOOST_PROCESS_USE_STD_FS
1430-
#include <boost/process.hpp>
1431-
]],[[
1432-
namespace bp = boost::process;
1433-
bp::opstream stdin_stream;
1434-
bp::ipstream stdout_stream;
1435-
bp::child c("dummy", bp::std_out > stdout_stream, bp::std_err > stdout_stream, bp::std_in < stdin_stream);
1436-
stdin_stream << std::string{"test"} << std::endl;
1437-
if (c.running()) c.terminate();
1438-
c.wait();
1439-
c.exit_code();
1440-
]])],
1441-
[have_boost_process="yes"],
1442-
[have_boost_process="no"])
1443-
LDFLAGS="$TEMP_LDFLAGS"
1444-
CPPFLAGS="$TEMP_CPPFLAGS"
1445-
CXXFLAGS="$TEMP_CXXFLAGS"
1446-
AC_MSG_RESULT([$have_boost_process])
1447-
if test "$have_boost_process" = "yes"; then
1448-
case $host in
1449-
dnl Boost Process for Windows uses Boost ASIO. Boost ASIO performs
1450-
dnl pre-main init of Windows networking libraries, which we do not
1451-
dnl want.
1452-
*mingw*)
1453-
use_external_signer="no"
1454-
;;
1455-
*)
1456-
use_external_signer="yes"
1457-
AC_DEFINE([ENABLE_EXTERNAL_SIGNER], [1], [Define if external signer support is enabled])
1458-
AC_DEFINE([BOOST_PROCESS_USE_STD_FS], [1], [Defined to avoid Boost::Process trying to use Boost Filesystem])
1459-
;;
1460-
esac
1461-
else
1462-
if test "$use_external_signer" = "yes"; then
1463-
AC_MSG_ERROR([External signing is not supported for this Boost version])
1464-
fi
1465-
use_external_signer="no";
1466-
fi
1417+
case $host in
1418+
dnl Re-enable it after enabling Windows support in cpp-subprocess.
1419+
*mingw*)
1420+
use_external_signer="no"
1421+
;;
1422+
esac
1423+
if test "$use_external_signer" = "yes"; then
1424+
AC_DEFINE([ENABLE_EXTERNAL_SIGNER], [1], [Define if external signer support is enabled])
14671425
fi
14681426
AM_CONDITIONAL([ENABLE_EXTERNAL_SIGNER], [test "$use_external_signer" = "yes"])
14691427

src/Makefile.am

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -320,6 +320,7 @@ BITCOIN_CORE_H = \
320320
util/sock.h \
321321
util/spanparsing.h \
322322
util/string.h \
323+
util/subprocess.hpp \
323324
util/syserror.h \
324325
util/task_runner.h \
325326
util/thread.h \

src/common/run_command.cpp

Lines changed: 10 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -12,39 +12,34 @@
1212
#include <univalue.h>
1313

1414
#ifdef ENABLE_EXTERNAL_SIGNER
15-
#include <boost/process.hpp>
15+
#include <util/subprocess.hpp>
1616
#endif // ENABLE_EXTERNAL_SIGNER
1717

1818
UniValue RunCommandParseJSON(const std::string& str_command, const std::string& str_std_in)
1919
{
2020
#ifdef ENABLE_EXTERNAL_SIGNER
21-
namespace bp = boost::process;
21+
namespace sp = subprocess;
2222

2323
UniValue result_json;
24-
bp::opstream stdin_stream;
25-
bp::ipstream stdout_stream;
26-
bp::ipstream stderr_stream;
24+
std::istringstream stdout_stream;
25+
std::istringstream stderr_stream;
2726

2827
if (str_command.empty()) return UniValue::VNULL;
2928

30-
bp::child c(
31-
str_command,
32-
bp::std_out > stdout_stream,
33-
bp::std_err > stderr_stream,
34-
bp::std_in < stdin_stream
35-
);
29+
auto c = sp::Popen(str_command, sp::input{sp::PIPE}, sp::output{sp::PIPE}, sp::error{sp::PIPE});
3630
if (!str_std_in.empty()) {
37-
stdin_stream << str_std_in << std::endl;
31+
c.send(str_std_in);
3832
}
39-
stdin_stream.pipe().close();
33+
auto [out_res, err_res] = c.communicate();
34+
stdout_stream.str(std::string{out_res.buf.begin(), out_res.buf.end()});
35+
stderr_stream.str(std::string{err_res.buf.begin(), err_res.buf.end()});
4036

4137
std::string result;
4238
std::string error;
4339
std::getline(stdout_stream, result);
4440
std::getline(stderr_stream, error);
4541

46-
c.wait();
47-
const int n_error = c.exit_code();
42+
const int n_error = c.retcode();
4843
if (n_error) throw std::runtime_error(strprintf("RunCommandParseJSON error: process(%s) returned %d: %s\n", str_command, n_error, error));
4944
if (!result_json.read(result)) throw std::runtime_error("Unable to parse JSON: " + result);
5045

src/external_signer.cpp

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -62,12 +62,12 @@ bool ExternalSigner::Enumerate(const std::string& command, std::vector<ExternalS
6262

6363
UniValue ExternalSigner::DisplayAddress(const std::string& descriptor) const
6464
{
65-
return RunCommandParseJSON(m_command + " --fingerprint \"" + m_fingerprint + "\"" + NetworkArg() + " displayaddress --desc \"" + descriptor + "\"");
65+
return RunCommandParseJSON(m_command + " --fingerprint " + m_fingerprint + NetworkArg() + " displayaddress --desc " + descriptor);
6666
}
6767

6868
UniValue ExternalSigner::GetDescriptors(const int account)
6969
{
70-
return RunCommandParseJSON(m_command + " --fingerprint \"" + m_fingerprint + "\"" + NetworkArg() + " getdescriptors --account " + strprintf("%d", account));
70+
return RunCommandParseJSON(m_command + " --fingerprint " + m_fingerprint + NetworkArg() + " getdescriptors --account " + strprintf("%d", account));
7171
}
7272

7373
bool ExternalSigner::SignTransaction(PartiallySignedTransaction& psbtx, std::string& error)
@@ -93,8 +93,8 @@ bool ExternalSigner::SignTransaction(PartiallySignedTransaction& psbtx, std::str
9393
return false;
9494
}
9595

96-
const std::string command = m_command + " --stdin --fingerprint \"" + m_fingerprint + "\"" + NetworkArg();
97-
const std::string stdinStr = "signtx \"" + EncodeBase64(ssTx.str()) + "\"";
96+
const std::string command = m_command + " --stdin --fingerprint " + m_fingerprint + NetworkArg();
97+
const std::string stdinStr = "signtx " + EncodeBase64(ssTx.str());
9898

9999
const UniValue signer_result = RunCommandParseJSON(command, stdinStr);
100100

src/test/system_tests.cpp

Lines changed: 5 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
#include <univalue.h>
1212

1313
#ifdef ENABLE_EXTERNAL_SIGNER
14-
#include <boost/process.hpp>
14+
#include <util/subprocess.hpp>
1515
#endif // ENABLE_EXTERNAL_SIGNER
1616

1717
#include <boost/test/unit_test.hpp>
@@ -34,20 +34,16 @@ BOOST_AUTO_TEST_CASE(run_command)
3434
BOOST_CHECK(result.isNull());
3535
}
3636
{
37-
const UniValue result = RunCommandParseJSON("echo \"{\"success\": true}\"");
37+
const UniValue result = RunCommandParseJSON("echo {\"success\": true}");
3838
BOOST_CHECK(result.isObject());
3939
const UniValue& success = result.find_value("success");
4040
BOOST_CHECK(!success.isNull());
4141
BOOST_CHECK_EQUAL(success.get_bool(), true);
4242
}
4343
{
44-
// An invalid command is handled by Boost
45-
const int expected_error{2};
46-
BOOST_CHECK_EXCEPTION(RunCommandParseJSON("invalid_command"), boost::process::process_error, [&](const boost::process::process_error& e) {
47-
BOOST_CHECK(std::string(e.what()).find("RunCommandParseJSON error:") == std::string::npos);
48-
BOOST_CHECK_EQUAL(e.code().value(), expected_error);
49-
return true;
50-
});
44+
// An invalid command is handled by cpp-subprocess
45+
const std::string expected{"execve failed: "};
46+
BOOST_CHECK_EXCEPTION(RunCommandParseJSON("invalid_command"), subprocess::CalledProcessError, HasReason(expected));
5147
}
5248
{
5349
// Return non-zero exit code, no output to stderr

0 commit comments

Comments
 (0)