mirror of
https://github.com/bitcoin/bitcoin.git
synced 2025-01-25 02:33:24 -03:00
multiprocess: Add bitcoin wrapper executable
Intended to make bitcoin command line features more discoverable and allow installing new multiprocess binaries in libexec/ instead of bin/ so they don't cause confusion. Idea and implementation of this were discussed in https://github.com/bitcoin/bitcoin/issues/30983 It's possible to debug this and see what programs it is trying to call by running: strace -e trace=execve -s 10000 build/src/bitcoin ...
This commit is contained in:
parent
62bd61de11
commit
ce3b0ca72d
5 changed files with 227 additions and 0 deletions
|
@ -76,6 +76,7 @@ list(APPEND CMAKE_MODULE_PATH ${PROJECT_SOURCE_DIR}/cmake/module)
|
|||
#=============================
|
||||
include(CMakeDependentOption)
|
||||
# When adding a new option, end the <help_text> with a full stop for consistency.
|
||||
option(BUILD_BITCOIN_BIN "Build bitcoin executable." ON)
|
||||
option(BUILD_DAEMON "Build bitcoind executable." ON)
|
||||
option(BUILD_GUI "Build bitcoin-qt executable." OFF)
|
||||
option(BUILD_CLI "Build bitcoin-cli executable." ON)
|
||||
|
@ -210,6 +211,7 @@ target_link_libraries(core_interface INTERFACE
|
|||
|
||||
if(BUILD_FOR_FUZZING)
|
||||
message(WARNING "BUILD_FOR_FUZZING=ON will disable all other targets and force BUILD_FUZZ_BINARY=ON.")
|
||||
set(BUILD_BITCOIN_BIN OFF)
|
||||
set(BUILD_DAEMON OFF)
|
||||
set(BUILD_CLI OFF)
|
||||
set(BUILD_TX OFF)
|
||||
|
@ -593,6 +595,7 @@ message("\n")
|
|||
message("Configure summary")
|
||||
message("=================")
|
||||
message("Executables:")
|
||||
message(" bitcoin ............................. ${BUILD_BITCOIN_BIN}")
|
||||
message(" bitcoind ............................ ${BUILD_DAEMON}")
|
||||
message(" bitcoin-node (multiprocess) ......... ${WITH_MULTIPROCESS}")
|
||||
message(" bitcoin-qt (GUI) .................... ${BUILD_GUI}")
|
||||
|
|
|
@ -305,6 +305,11 @@ target_link_libraries(bitcoin_node
|
|||
$<TARGET_NAME_IF_EXISTS:USDT::headers>
|
||||
)
|
||||
|
||||
# Bitcoin wrapper executable that can call other executables.
|
||||
if(BUILD_BITCOIN_BIN)
|
||||
add_executable(bitcoin bitcoin.cpp)
|
||||
target_link_libraries(bitcoin core_interface bitcoin_util)
|
||||
endif()
|
||||
|
||||
# Bitcoin Core bitcoind.
|
||||
if(BUILD_DAEMON)
|
||||
|
|
213
src/bitcoin.cpp
Normal file
213
src/bitcoin.cpp
Normal file
|
@ -0,0 +1,213 @@
|
|||
// Copyright (c) 2024 The Bitcoin Core developers
|
||||
// Distributed under the MIT software license, see the accompanying
|
||||
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
|
||||
|
||||
#include <bitcoin-build-config.h> // IWYU pragma: keep
|
||||
|
||||
#include <clientversion.h>
|
||||
#include <util/fs.h>
|
||||
#include <util/strencodings.h>
|
||||
|
||||
#include <iostream>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <tinyformat.h>
|
||||
#include <vector>
|
||||
|
||||
#ifdef WIN32
|
||||
#include <process.h>
|
||||
#include <windows.h>
|
||||
#define execvp _execvp
|
||||
#else
|
||||
#include <unistd.h>
|
||||
#endif
|
||||
|
||||
extern const std::function<std::string(const char*)> G_TRANSLATION_FUN = nullptr;
|
||||
|
||||
static constexpr auto HELP_USAGE = R"(Usage: %1$s [OPTIONS] COMMAND...
|
||||
|
||||
Commands (run help command for more information):
|
||||
{gui,daemon,rpc,wallet,test,help}
|
||||
|
||||
Options:
|
||||
-m, --multiprocess Run multiprocess binaries bitcoin-node, bitcoin-gui.\n"
|
||||
-M, --monolothic Run monolithic binaries bitcoind, bitcoin-qt. (Default behavior)\n"
|
||||
-v, --version Show version information
|
||||
-h, --help Show this help message
|
||||
)";
|
||||
|
||||
static constexpr auto HELP_COMMANDS = R"(Command overview:
|
||||
|
||||
%1$s gui [ARGS] Start GUI, equivalent to running 'bitcoin-qt [ARGS]' or 'bitcoin-gui [ARGS]'.
|
||||
%1$s daemon [ARGS] Start daemon, equivalent to running 'bitcoind [ARGS]' or 'bitcoin-node [ARGS]'.
|
||||
%1$s rpc [ARGS] Call RPC method, equivalent to running 'bitcoin-cli -named [ARGS]'.
|
||||
%1$s wallet [ARGS] Call wallet command, equivalent to running 'bitcoin-wallet [ARGS]'.
|
||||
%1$s tx [ARGS] Manipulate hex-encoded transactions, equivalent to running 'bitcoin-tx [ARGS]'.
|
||||
%1$s test [ARGS] Run unit tests, equivalent to running 'test_bitcoin [ARGS]'.
|
||||
%1$s help Show this help message.
|
||||
)";
|
||||
|
||||
struct CommandLine {
|
||||
bool use_multiprocess{false};
|
||||
bool show_version{false};
|
||||
bool show_help{false};
|
||||
std::string_view command;
|
||||
std::vector<std::string_view> args;
|
||||
};
|
||||
|
||||
CommandLine ParseCommandLine(int argc, char* argv[]);
|
||||
void ExecCommand(const std::vector<std::string>& args, std::string_view argv0);
|
||||
|
||||
int main(int argc, char* argv[])
|
||||
{
|
||||
try {
|
||||
CommandLine cmd{ParseCommandLine(argc, argv)};
|
||||
if (cmd.show_version) {
|
||||
tfm::format(std::cout, "%s version %s\n%s", CLIENT_NAME, FormatFullVersion(), FormatParagraph(LicenseInfo()));
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
if (cmd.show_help || cmd.command.empty()) {
|
||||
tfm::format(std::cout, HELP_USAGE, argv[0]);
|
||||
}
|
||||
|
||||
std::vector<std::string> args;
|
||||
if (cmd.command == "gui") {
|
||||
args.emplace_back(cmd.use_multiprocess ? "qt/bitcoin-gui" : "qt/bitcoin-qt");
|
||||
} else if (cmd.command == "daemon") {
|
||||
args.emplace_back(cmd.use_multiprocess ? "bitcoin-node" : "bitcoind");
|
||||
} else if (cmd.command == "rpc") {
|
||||
args.emplace_back("bitcoin-cli");
|
||||
args.emplace_back("-named");
|
||||
} else if (cmd.command == "wallet") {
|
||||
args.emplace_back("bitcoin-wallet");
|
||||
} else if (cmd.command == "tx") {
|
||||
args.emplace_back("bitcoin-tx");
|
||||
} else if (cmd.command == "test") {
|
||||
args.emplace_back("test/test_bitcoin");
|
||||
} else if (cmd.command == "help") {
|
||||
tfm::format(std::cout, HELP_COMMANDS, argv[0]);
|
||||
} else if (cmd.command == "mine") { // undocumented, used by tests
|
||||
args.emplace_back("bitcoin-mine");
|
||||
} else if (cmd.command == "util") { // undocumented, used by tests
|
||||
args.emplace_back("bitcoin-util");
|
||||
} else if (!cmd.command.empty()){
|
||||
throw std::runtime_error(strprintf("Unrecognized command: '%s'", cmd.command));
|
||||
}
|
||||
if (!args.empty()) {
|
||||
args.insert(args.end(), cmd.args.begin(), cmd.args.end());
|
||||
ExecCommand(args, argv[0]);
|
||||
}
|
||||
} catch (const std::exception& e) {
|
||||
tfm::format(std::cerr, "Error: %s\nTry '%s --help' for more information.\n", e.what(), argv[0]);
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
|
||||
CommandLine ParseCommandLine(int argc, char* argv[])
|
||||
{
|
||||
CommandLine cmd;
|
||||
cmd.args.reserve(argc);
|
||||
for (int i = 1; i < argc; ++i) {
|
||||
std::string_view arg = argv[i];
|
||||
if (!cmd.command.empty()) {
|
||||
cmd.args.emplace_back(arg);
|
||||
} else if (arg == "-m" || arg == "--multiprocess") {
|
||||
cmd.use_multiprocess = true;
|
||||
} else if (arg == "-M" || arg == "--monolithic") {
|
||||
cmd.use_multiprocess = false;
|
||||
} else if (arg == "-v" || arg == "--version") {
|
||||
cmd.show_version = true;
|
||||
} else if (arg == "-h" || arg == "--help") {
|
||||
cmd.show_help = true;
|
||||
} else if (arg.starts_with("-")) {
|
||||
throw std::runtime_error(strprintf("Unknown option: %s", arg));
|
||||
} else if (!arg.empty()) {
|
||||
cmd.command = arg;
|
||||
}
|
||||
}
|
||||
return cmd;
|
||||
}
|
||||
|
||||
// Execute the specified bitcoind, bitcoin-qt or other command line in `args`
|
||||
// using src, bin and libexec directory paths relative to this executable, where
|
||||
// the path to this executable is specified `argv0`.
|
||||
//
|
||||
// This function doesn't currently print anything but can be debugged from
|
||||
// the command line using strace like:
|
||||
//
|
||||
// strace -e trace=execve -s 10000 build/src/bitcoin ...
|
||||
void ExecCommand(const std::vector<std::string>& args, std::string_view argv0)
|
||||
{
|
||||
// Construct argument string for execvp
|
||||
std::vector<const char*> cstr_args{};
|
||||
cstr_args.reserve(args.size() + 1);
|
||||
for (const auto& arg : args) {
|
||||
cstr_args.emplace_back(arg.c_str());
|
||||
}
|
||||
cstr_args.emplace_back(nullptr);
|
||||
|
||||
// Try to call execvp with given exe path.
|
||||
auto try_exec = [&](fs::path exe_path, bool allow_notfound = true) {
|
||||
std::string exe_path_str{fs::PathToString(exe_path)};
|
||||
cstr_args[0] = exe_path_str.c_str();
|
||||
if (execvp(cstr_args[0], (char*const*)cstr_args.data()) == -1) {
|
||||
if (allow_notfound && errno == ENOENT) return false;
|
||||
throw std::system_error(errno, std::system_category(), strprintf("execvp failed to execute '%s'", cstr_args[0]));
|
||||
}
|
||||
return true; // Will not actually be reached if execvp succeeds
|
||||
};
|
||||
|
||||
// Try to figure out where current executable is located. This is a
|
||||
// simplified search that won't work perfectly on every platform and doesn't
|
||||
// need to, as it is only trying to prioritize locally built or installed
|
||||
// executables over system executables. We may want to add options to
|
||||
// override this behavior in the future, though.
|
||||
const fs::path argv0_path{fs::PathFromString(std::string{argv0})};
|
||||
fs::path exe_path{argv0_path};
|
||||
std::error_code ec;
|
||||
#ifndef WIN32
|
||||
if (argv0.find('/') == std::string_view::npos) {
|
||||
if (const char* path_env = std::getenv("PATH")) {
|
||||
size_t start{0}, end{0};
|
||||
for (std::string_view paths{path_env}; end != std::string_view::npos; start = end + 1) {
|
||||
end = paths.find(':', start);
|
||||
fs::path candidate = fs::path(paths.substr(start, end - start)) / argv0_path;
|
||||
if (fs::exists(candidate, ec) && fs::is_regular_file(candidate, ec)) {
|
||||
exe_path = candidate;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
#else
|
||||
wchar_t module_path[MAX_PATH];
|
||||
if (GetModuleFileNameW(nullptr, module_path, MAX_PATH) > 0) {
|
||||
exe_path = fs::path{module_path};
|
||||
} else {
|
||||
tfm::format(std::cerr, "Warning: Failed to get executable path. Error: %s\n", GetLastError());
|
||||
}
|
||||
#endif
|
||||
|
||||
// Try to resolve any symlinks and figure out actual directory containing this executable.
|
||||
fs::path exe_dir{fs::weakly_canonical(exe_path, ec)};
|
||||
if (exe_dir.empty()) exe_dir = exe_path; // Restore previous path if weakly_canonical failed.
|
||||
exe_dir = exe_dir.parent_path();
|
||||
// Search for executables on system PATH only if this executable was invoked
|
||||
// from the PATH, to avoid unintentionally launching system executables in a
|
||||
// local build
|
||||
// (https://github.com/bitcoin/bitcoin/pull/31375#discussion_r1861814807)
|
||||
const bool use_system_path{!argv0_path.has_parent_path()};
|
||||
const fs::path arg0{fs::PathFromString(args[0])};
|
||||
|
||||
// If exe is in a CMake build tree, first look for target executable
|
||||
// relative to it.
|
||||
(exe_dir.filename() == "src" && try_exec(exe_dir / arg0)) ||
|
||||
// Otherwise if exe is installed in a bin/ directory, first look for target
|
||||
// executable in libexec/
|
||||
(exe_dir.filename() == "bin" && try_exec(fs::path{exe_dir.parent_path()} / "libexec" / arg0.filename())) ||
|
||||
// Otherwise look for target executable next to current exe
|
||||
try_exec(exe_dir / arg0.filename(), use_system_path) ||
|
||||
// Otherwise just look on the system path.
|
||||
(use_system_path && try_exec(arg0.filename(), false));
|
||||
};
|
|
@ -90,6 +90,10 @@ static inline bool exists(const path& p)
|
|||
{
|
||||
return std::filesystem::exists(p);
|
||||
}
|
||||
static inline bool exists(const path& p, std::error_code& ec) noexcept
|
||||
{
|
||||
return std::filesystem::exists(p, ec);
|
||||
}
|
||||
|
||||
// Allow explicit quoted stream I/O.
|
||||
static inline auto quoted(const std::string& s)
|
||||
|
|
|
@ -13,6 +13,8 @@ import re
|
|||
import sys
|
||||
|
||||
FALSE_POSITIVES = [
|
||||
("src/bitcoin.cpp", "tfm::format(std::cout, HELP_USAGE, argv[0])"),
|
||||
("src/bitcoin.cpp", "tfm::format(std::cout, HELP_COMMANDS, argv[0])"),
|
||||
("src/clientversion.cpp", "strprintf(_(COPYRIGHT_HOLDERS), COPYRIGHT_HOLDERS_SUBSTITUTION)"),
|
||||
("src/test/translation_tests.cpp", "strprintf(format, arg)"),
|
||||
("src/test/util_string_tests.cpp", 'tfm::format(ConstevalFormatString<2>{"%*s"}, "hi", "hi")'),
|
||||
|
|
Loading…
Add table
Reference in a new issue