bitcoin.cpp
1 // Copyright (c) 2025-present 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/license_info.h> 10 #include <common/system.h> 11 #include <util/fs.h> 12 #include <util/exec.h> 13 #include <util/strencodings.h> 14 #include <util/translation.h> 15 16 #include <iostream> 17 #include <string> 18 #include <tinyformat.h> 19 #include <vector> 20 21 const TranslateFn G_TRANSLATION_FUN{nullptr}; 22 23 static constexpr auto HELP_USAGE = R"(Usage: %s [OPTIONS] COMMAND... 24 25 Options: 26 -m, --multiprocess Run multiprocess binaries bitcoin-node, bitcoin-gui. 27 -M, --monolithic Run monolithic binaries bitcoind, bitcoin-qt. (Default behavior) 28 -v, --version Show version information 29 -h, --help Show full help message 30 31 Commands: 32 gui [ARGS] Start GUI, equivalent to running 'bitcoin-qt [ARGS]' or 'bitcoin-gui [ARGS]'. 33 node [ARGS] Start node, equivalent to running 'bitcoind [ARGS]' or 'bitcoin-node [ARGS]'. 34 rpc [ARGS] Call RPC method, equivalent to running 'bitcoin-cli -named [ARGS]'. 35 wallet [ARGS] Call wallet command, equivalent to running 'bitcoin-wallet [ARGS]'. 36 tx [ARGS] Manipulate hex-encoded transactions, equivalent to running 'bitcoin-tx [ARGS]'. 37 help Show full help message. 38 )"; 39 40 static constexpr auto HELP_FULL = R"( 41 Additional less commonly used commands: 42 bench [ARGS] Run bench command, equivalent to running 'bench_bitcoin [ARGS]'. 43 chainstate [ARGS] Run bitcoin kernel chainstate util, equivalent to running 'bitcoin-chainstate [ARGS]'. 44 test [ARGS] Run unit tests, equivalent to running 'test_bitcoin [ARGS]'. 45 test-gui [ARGS] Run GUI unit tests, equivalent to running 'test_bitcoin-qt [ARGS]'. 46 )"; 47 48 static constexpr auto HELP_SHORT = R"( 49 Run '%s help' to see additional commands (e.g. for testing and debugging). 50 )"; 51 52 struct CommandLine { 53 std::optional<bool> use_multiprocess; 54 bool show_version{false}; 55 bool show_help{false}; 56 std::string_view command; 57 std::vector<const char*> args; 58 }; 59 60 CommandLine ParseCommandLine(int argc, char* argv[]); 61 bool UseMultiprocess(const CommandLine& cmd); 62 static void ExecCommand(const std::vector<const char*>& args, std::string_view argv0); 63 64 int main(int argc, char* argv[]) 65 { 66 SetupEnvironment(); 67 68 try { 69 CommandLine cmd{ParseCommandLine(argc, argv)}; 70 if (cmd.show_version) { 71 tfm::format(std::cout, "%s version %s\n%s", CLIENT_NAME, FormatFullVersion(), FormatParagraph(LicenseInfo())); 72 return EXIT_SUCCESS; 73 } 74 75 std::string exe_name{fs::PathToString(fs::PathFromString(argv[0]).filename())}; 76 std::vector<const char*> args; 77 if (cmd.show_help || cmd.command.empty()) { 78 tfm::format(std::cout, HELP_USAGE, exe_name); 79 if (cmd.show_help) { 80 tfm::format(std::cout, HELP_FULL); 81 return EXIT_SUCCESS; 82 } else { 83 tfm::format(std::cout, HELP_SHORT, exe_name); 84 return EXIT_FAILURE; 85 } 86 } else if (cmd.command == "gui") { 87 args.emplace_back(UseMultiprocess(cmd) ? "bitcoin-gui" : "bitcoin-qt"); 88 } else if (cmd.command == "node") { 89 args.emplace_back(UseMultiprocess(cmd) ? "bitcoin-node" : "bitcoind"); 90 } else if (cmd.command == "rpc") { 91 args.emplace_back("bitcoin-cli"); 92 // Since "bitcoin rpc" is a new interface that doesn't need to be 93 // backward compatible, enable -named by default so it is convenient 94 // for callers to use a mix of named and unnamed parameters. Callers 95 // can override this by specifying -nonamed, but it handles parameters 96 // that contain '=' characters, so -nonamed should rarely be needed. 97 args.emplace_back("-named"); 98 } else if (cmd.command == "wallet") { 99 args.emplace_back("bitcoin-wallet"); 100 } else if (cmd.command == "tx") { 101 args.emplace_back("bitcoin-tx"); 102 } else if (cmd.command == "bench") { 103 args.emplace_back("bench_bitcoin"); 104 } else if (cmd.command == "chainstate") { 105 args.emplace_back("bitcoin-chainstate"); 106 } else if (cmd.command == "test") { 107 args.emplace_back("test_bitcoin"); 108 } else if (cmd.command == "test-gui") { 109 args.emplace_back("test_bitcoin-qt"); 110 } else if (cmd.command == "util") { 111 args.emplace_back("bitcoin-util"); 112 } else { 113 throw std::runtime_error(strprintf("Unrecognized command: '%s'", cmd.command)); 114 } 115 if (!args.empty()) { 116 args.insert(args.end(), cmd.args.begin(), cmd.args.end()); 117 ExecCommand(args, argv[0]); 118 } 119 } catch (const std::exception& e) { 120 tfm::format(std::cerr, "Error: %s\nTry '%s --help' for more information.\n", e.what(), argv[0]); 121 return EXIT_FAILURE; 122 } 123 return EXIT_SUCCESS; 124 } 125 126 CommandLine ParseCommandLine(int argc, char* argv[]) 127 { 128 CommandLine cmd; 129 cmd.args.reserve(argc); 130 for (int i = 1; i < argc; ++i) { 131 std::string_view arg = argv[i]; 132 if (!cmd.command.empty()) { 133 cmd.args.emplace_back(argv[i]); 134 } else if (arg == "-m" || arg == "--multiprocess") { 135 cmd.use_multiprocess = true; 136 } else if (arg == "-M" || arg == "--monolithic") { 137 cmd.use_multiprocess = false; 138 } else if (arg == "-v" || arg == "--version") { 139 cmd.show_version = true; 140 } else if (arg == "-h" || arg == "--help" || arg == "help") { 141 cmd.show_help = true; 142 } else if (arg.starts_with("-")) { 143 throw std::runtime_error(strprintf("Unknown option: %s", arg)); 144 } else if (!arg.empty()) { 145 cmd.command = arg; 146 } 147 } 148 return cmd; 149 } 150 151 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 if (cmd.use_multiprocess) return *cmd.use_multiprocess; 156 157 ArgsManager args; 158 args.SetDefaultFlags(ArgsManager::ALLOW_ANY); 159 std::string error_message; 160 auto argv{cmd.args}; 161 argv.insert(argv.begin(), nullptr); 162 if (!args.ParseParameters(argv.size(), argv.data(), error_message)) { 163 tfm::format(std::cerr, "Warning: failed to parse subcommand command line options: %s\n", error_message); 164 } 165 if (!args.ReadConfigFiles(error_message, true)) { 166 tfm::format(std::cerr, "Warning: failed to parse subcommand config: %s\n", error_message); 167 } 168 args.SelectConfigNetwork(args.GetChainTypeString()); 169 170 // If any -ipc* options are set these need to be processed by a 171 // multiprocess-capable binary. 172 return args.IsArgSet("-ipcbind") || args.IsArgSet("-ipcconnect") || args.IsArgSet("-ipcfd"); 173 } 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 static void ExecCommand(const std::vector<const char*>& args, std::string_view wrapper_argv0) 194 { 195 // Construct argument string for execvp 196 std::vector<const char*> exec_args{args}; 197 exec_args.emplace_back(nullptr); 198 199 // Try to call ExecVp with given exe path. 200 auto try_exec = [&](fs::path exe_path, bool allow_notfound = true) { 201 std::string exe_path_str{fs::PathToString(exe_path)}; 202 exec_args[0] = exe_path_str.c_str(); 203 if (util::ExecVp(exec_args[0], (char*const*)exec_args.data()) == -1) { 204 if (allow_notfound && errno == ENOENT) return false; 205 throw std::system_error(errno, std::system_category(), strprintf("execvp failed to execute '%s'", exec_args[0])); 206 } 207 throw std::runtime_error("execvp returned unexpectedly"); 208 }; 209 210 // Get the wrapper executable path. 211 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 std::error_code ec; 215 auto wrapper_dir{fs::weakly_canonical(wrapper_path, ec)}; 216 if (wrapper_dir.empty()) wrapper_dir = wrapper_path; // Restore previous path if weakly_canonical failed. 217 wrapper_dir = wrapper_dir.parent_path(); 218 219 // Get path of the executable to be invoked. 220 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 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 (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 (!wrapper_dir.empty() && try_exec(wrapper_dir / arg0.filename(), fallback_os_search)) || 238 // Otherwise just look on the system path. 239 (fallback_os_search && try_exec(arg0.filename(), false)); 240 }