Branch data Line data Source code
1 : : // Copyright (c) 2025 The Bitcoin Core developers
2 : : // Distributed under the MIT software license, see the accompanying
3 : : // file COPYING or http://www.opensource.org/licenses/mit-license.php.
4 : :
5 : : #include <bitcoin-build-config.h> // IWYU pragma: keep
6 : :
7 : : #include <clientversion.h>
8 : : #include <common/args.h>
9 : : #include <common/system.h>
10 : : #include <util/fs.h>
11 : : #include <util/exec.h>
12 : : #include <util/strencodings.h>
13 : : #include <util/translation.h>
14 : :
15 : : #include <iostream>
16 : : #include <string>
17 : : #include <tinyformat.h>
18 : : #include <vector>
19 : :
20 : : const TranslateFn G_TRANSLATION_FUN{nullptr};
21 : :
22 : : static constexpr auto HELP_USAGE = R"(Usage: %s [OPTIONS] COMMAND...
23 : :
24 : : Options:
25 : : -m, --multiprocess Run multiprocess binaries bitcoin-node, bitcoin-gui.
26 : : -M, --monolithic Run monolithic binaries bitcoind, bitcoin-qt. (Default behavior)
27 : : -v, --version Show version information
28 : : -h, --help Show full help message
29 : :
30 : : Commands:
31 : : gui [ARGS] Start GUI, equivalent to running 'bitcoin-qt [ARGS]' or 'bitcoin-gui [ARGS]'.
32 : : node [ARGS] Start node, equivalent to running 'bitcoind [ARGS]' or 'bitcoin-node [ARGS]'.
33 : : rpc [ARGS] Call RPC method, equivalent to running 'bitcoin-cli -named [ARGS]'.
34 : : wallet [ARGS] Call wallet command, equivalent to running 'bitcoin-wallet [ARGS]'.
35 : : tx [ARGS] Manipulate hex-encoded transactions, equivalent to running 'bitcoin-tx [ARGS]'.
36 : : help Show full help message.
37 : : )";
38 : :
39 : : static constexpr auto HELP_FULL = R"(
40 : : Additional less commonly used commands:
41 : : bench [ARGS] Run bench command, equivalent to running 'bench_bitcoin [ARGS]'.
42 : : chainstate [ARGS] Run bitcoin kernel chainstate util, equivalent to running 'bitcoin-chainstate [ARGS]'.
43 : : test [ARGS] Run unit tests, equivalent to running 'test_bitcoin [ARGS]'.
44 : : test-gui [ARGS] Run GUI unit tests, equivalent to running 'test_bitcoin-qt [ARGS]'.
45 : : )";
46 : :
47 : : static constexpr auto HELP_SHORT = R"(
48 : : Run '%s help' to see additional commands (e.g. for testing and debugging).
49 : : )";
50 : :
51 : 0 : struct CommandLine {
52 : : std::optional<bool> use_multiprocess;
53 : : bool show_version{false};
54 : : bool show_help{false};
55 : : std::string_view command;
56 : : std::vector<const char*> args;
57 : : };
58 : :
59 : : CommandLine ParseCommandLine(int argc, char* argv[]);
60 : : bool UseMultiprocess(const CommandLine& cmd);
61 : : static void ExecCommand(const std::vector<const char*>& args, std::string_view argv0);
62 : :
63 : 0 : int main(int argc, char* argv[])
64 : : {
65 : 0 : SetupEnvironment();
66 : :
67 : 0 : try {
68 [ # # ]: 0 : CommandLine cmd{ParseCommandLine(argc, argv)};
69 [ # # ]: 0 : if (cmd.show_version) {
70 [ # # # # : 0 : tfm::format(std::cout, "%s version %s\n%s", CLIENT_NAME, FormatFullVersion(), FormatParagraph(LicenseInfo()));
# # # # ]
71 : 0 : return EXIT_SUCCESS;
72 : : }
73 : :
74 [ # # # # : 0 : std::string exe_name{fs::PathToString(fs::PathFromString(argv[0]).filename())};
# # # # ]
75 : 0 : std::vector<const char*> args;
76 [ # # # # ]: 0 : if (cmd.show_help || cmd.command.empty()) {
77 [ # # ]: 0 : tfm::format(std::cout, HELP_USAGE, exe_name);
78 [ # # ]: 0 : if (cmd.show_help) {
79 [ # # ]: 0 : tfm::format(std::cout, HELP_FULL);
80 : : return EXIT_SUCCESS;
81 : : } else {
82 [ # # ]: 0 : tfm::format(std::cout, HELP_SHORT, exe_name);
83 : : return EXIT_FAILURE;
84 : : }
85 [ # # ]: 0 : } else if (cmd.command == "gui") {
86 [ # # # # : 0 : args.emplace_back(UseMultiprocess(cmd) ? "bitcoin-gui" : "bitcoin-qt");
# # ]
87 [ # # ]: 0 : } else if (cmd.command == "node") {
88 [ # # # # : 0 : args.emplace_back(UseMultiprocess(cmd) ? "bitcoin-node" : "bitcoind");
# # ]
89 [ # # ]: 0 : } else if (cmd.command == "rpc") {
90 [ # # ]: 0 : args.emplace_back("bitcoin-cli");
91 : : // Since "bitcoin rpc" is a new interface that doesn't need to be
92 : : // backward compatible, enable -named by default so it is convenient
93 : : // for callers to use a mix of named and unnamed parameters. Callers
94 : : // can override this by specifying -nonamed, but should not need to
95 : : // unless they are passing string values containing '=' characters
96 : : // as unnamed parameters.
97 [ # # ]: 0 : args.emplace_back("-named");
98 [ # # ]: 0 : } else if (cmd.command == "wallet") {
99 [ # # ]: 0 : args.emplace_back("bitcoin-wallet");
100 [ # # ]: 0 : } else if (cmd.command == "tx") {
101 [ # # ]: 0 : args.emplace_back("bitcoin-tx");
102 [ # # ]: 0 : } else if (cmd.command == "bench") {
103 [ # # ]: 0 : args.emplace_back("bench_bitcoin");
104 [ # # ]: 0 : } else if (cmd.command == "chainstate") {
105 [ # # ]: 0 : args.emplace_back("bitcoin-chainstate");
106 [ # # ]: 0 : } else if (cmd.command == "test") {
107 [ # # ]: 0 : args.emplace_back("test_bitcoin");
108 [ # # ]: 0 : } else if (cmd.command == "test-gui") {
109 [ # # ]: 0 : args.emplace_back("test_bitcoin-qt");
110 [ # # ]: 0 : } else if (cmd.command == "util") {
111 [ # # ]: 0 : args.emplace_back("bitcoin-util");
112 : : } else {
113 [ # # # # ]: 0 : throw std::runtime_error(strprintf("Unrecognized command: '%s'", cmd.command));
114 : : }
115 [ # # ]: 0 : if (!args.empty()) {
116 [ # # ]: 0 : args.insert(args.end(), cmd.args.begin(), cmd.args.end());
117 [ # # ]: 0 : ExecCommand(args, argv[0]);
118 : : }
119 [ # # ]: 0 : } catch (const std::exception& e) {
120 [ - - ]: 0 : tfm::format(std::cerr, "Error: %s\nTry '%s --help' for more information.\n", e.what(), argv[0]);
121 : 0 : return EXIT_FAILURE;
122 : 0 : }
123 : 0 : return EXIT_SUCCESS;
124 : : }
125 : :
126 : 0 : CommandLine ParseCommandLine(int argc, char* argv[])
127 : : {
128 : 0 : CommandLine cmd;
129 [ # # ]: 0 : cmd.args.reserve(argc);
130 [ # # ]: 0 : for (int i = 1; i < argc; ++i) {
131 [ # # ]: 0 : std::string_view arg = argv[i];
132 [ # # ]: 0 : if (!cmd.command.empty()) {
133 [ # # ]: 0 : cmd.args.emplace_back(argv[i]);
134 [ # # # # ]: 0 : } else if (arg == "-m" || arg == "--multiprocess") {
135 : 0 : cmd.use_multiprocess = true;
136 [ # # # # ]: 0 : } else if (arg == "-M" || arg == "--monolithic") {
137 : 0 : cmd.use_multiprocess = false;
138 [ # # # # ]: 0 : } else if (arg == "-v" || arg == "--version") {
139 : 0 : cmd.show_version = true;
140 [ # # # # : 0 : } else if (arg == "-h" || arg == "--help" || arg == "help") {
# # ]
141 : 0 : cmd.show_help = true;
142 [ # # ]: 0 : } else if (arg.starts_with("-")) {
143 [ # # # # ]: 0 : throw std::runtime_error(strprintf("Unknown option: %s", arg));
144 [ # # ]: 0 : } else if (!arg.empty()) {
145 : 0 : cmd.command = arg;
146 : : }
147 : : }
148 : 0 : return cmd;
149 : 0 : }
150 : :
151 : 0 : bool UseMultiprocess(const CommandLine& cmd)
152 : : {
153 : : // If -m or -M options were explicitly specified, there is no need to
154 : : // further parse arguments to determine which to use.
155 [ # # ]: 0 : if (cmd.use_multiprocess) return *cmd.use_multiprocess;
156 : :
157 : 0 : ArgsManager args;
158 [ # # ]: 0 : args.SetDefaultFlags(ArgsManager::ALLOW_ANY);
159 [ # # ]: 0 : std::string error_message;
160 [ # # ]: 0 : auto argv{cmd.args};
161 [ # # ]: 0 : argv.insert(argv.begin(), nullptr);
162 [ # # # # : 0 : if (!args.ParseParameters(argv.size(), argv.data(), error_message)) {
# # ]
163 [ # # ]: 0 : tfm::format(std::cerr, "Warning: failed to parse subcommand command line options: %s\n", error_message);
164 : : }
165 [ # # # # ]: 0 : if (!args.ReadConfigFiles(error_message, true)) {
166 [ # # ]: 0 : tfm::format(std::cerr, "Warning: failed to parse subcommand config: %s\n", error_message);
167 : : }
168 [ # # # # ]: 0 : args.SelectConfigNetwork(args.GetChainTypeString());
169 : :
170 : : // If any -ipc* options are set these need to be processed by a
171 : : // multiprocess-capable binary.
172 [ # # # # : 0 : return args.IsArgSet("-ipcbind") || args.IsArgSet("-ipcconnect") || args.IsArgSet("-ipcfd");
# # # # #
# # # # #
# # # # #
# # # #
# ]
173 : 0 : }
174 : :
175 : : //! Execute the specified bitcoind, bitcoin-qt or other command line in `args`
176 : : //! using src, bin and libexec directory paths relative to this executable, where
177 : : //! the path to this executable is specified in `wrapper_argv0`.
178 : : //!
179 : : //! @param args Command line arguments to execute, where first argument should
180 : : //! be a relative path to a bitcoind, bitcoin-qt or other executable
181 : : //! that will be located on the PATH or relative to wrapper_argv0.
182 : : //!
183 : : //! @param wrapper_argv0 String containing first command line argument passed to
184 : : //! main() to run the current executable. This is used to
185 : : //! help determine the path to the current executable and
186 : : //! how to look for new executables.
187 : : //
188 : : //! @note This function doesn't currently print anything but can be debugged
189 : : //! from the command line using strace or dtrace like:
190 : : //!
191 : : //! strace -e trace=execve -s 10000 build/bin/bitcoin ...
192 : : //! dtrace -n 'proc:::exec-success /pid == $target/ { trace(curpsinfo->pr_psargs); }' -c ...
193 : 0 : static void ExecCommand(const std::vector<const char*>& args, std::string_view wrapper_argv0)
194 : : {
195 : : // Construct argument string for execvp
196 : 0 : std::vector<const char*> exec_args{args};
197 [ # # ]: 0 : exec_args.emplace_back(nullptr);
198 : :
199 : : // Try to call ExecVp with given exe path.
200 : 0 : auto try_exec = [&](fs::path exe_path, bool allow_notfound = true) {
201 [ # # ]: 0 : std::string exe_path_str{fs::PathToString(exe_path)};
202 [ # # ]: 0 : exec_args[0] = exe_path_str.c_str();
203 [ # # # # ]: 0 : if (util::ExecVp(exec_args[0], (char*const*)exec_args.data()) == -1) {
204 [ # # # # ]: 0 : if (allow_notfound && errno == ENOENT) return false;
205 [ # # # # ]: 0 : throw std::system_error(errno, std::system_category(), strprintf("execvp failed to execute '%s'", exec_args[0]));
206 : : }
207 [ # # ]: 0 : throw std::runtime_error("execvp returned unexpectedly");
208 : 0 : };
209 : :
210 : : // Get the wrapper executable path.
211 [ # # ]: 0 : const fs::path wrapper_path{util::GetExePath(wrapper_argv0)};
212 : :
213 : : // Try to resolve any symlinks and figure out the directory containing the wrapper executable.
214 [ # # ]: 0 : std::error_code ec;
215 [ # # ]: 0 : auto wrapper_dir{fs::weakly_canonical(wrapper_path, ec)};
216 [ # # # # ]: 0 : if (wrapper_dir.empty()) wrapper_dir = wrapper_path; // Restore previous path if weakly_canonical failed.
217 [ # # ]: 0 : wrapper_dir = wrapper_dir.parent_path();
218 : :
219 : : // Get path of the executable to be invoked.
220 [ # # # # ]: 0 : const fs::path arg0{fs::PathFromString(args[0])};
221 : :
222 : : // Decide whether to fall back to the operating system to search for the
223 : : // specified executable. Avoid doing this if it looks like the wrapper
224 : : // executable was invoked by path, rather than by search, to avoid
225 : : // unintentionally launching system executables in a local build.
226 : : // (https://github.com/bitcoin/bitcoin/pull/31375#discussion_r1861814807)
227 [ # # # # ]: 0 : const bool fallback_os_search{!fs::PathFromString(std::string{wrapper_argv0}).has_parent_path()};
228 : :
229 : : // If wrapper is installed in a bin/ directory, look for target executable
230 : : // in libexec/
231 [ # # # # : 0 : (wrapper_dir.filename() == "bin" && try_exec(wrapper_dir.parent_path() / "libexec" / arg0.filename())) ||
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # ]
232 : : #ifdef WIN32
233 : : // Otherwise check the "daemon" subdirectory in a windows install.
234 : : (!wrapper_dir.empty() && try_exec(wrapper_dir / "daemon" / arg0.filename())) ||
235 : : #endif
236 : : // Otherwise look for target executable next to current wrapper
237 [ # # # # : 0 : (!wrapper_dir.empty() && try_exec(wrapper_dir / arg0.filename(), fallback_os_search)) ||
# # # # #
# # # # #
# # # # #
# # # # #
# # ]
238 : : // Otherwise just look on the system path.
239 [ # # # # : 0 : (fallback_os_search && try_exec(arg0.filename(), false));
# # # # ]
240 : 0 : }
|