Repository: bitcoin/bitcoin
File: src/bitcoin.cpp (C++)

Code before:
        std::string_view arg = argv[i];
        if (!cmd.command.empty()) {
            cmd.args.emplace_back(argv[i]);
        } 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" || arg == "help") {
            cmd.show_help = true;
        } else if (cmd.show_help && (arg == "-a" || arg == "--all")) {
            cmd.show_help_all = true;
        } else if (arg.starts_with("-")) {
            throw std::runtime_error(strprintf("Unknown option: %s", arg));
        } else if (!arg.empty()) {
            cmd.command = arg;
        }
    }
    return cmd;
}

//! Cross-platform wrapper for POSIX execvp function
int ExecVp(const char *file, char *const argv[])
{
#ifndef WIN32
    return execvp(file, argv);
#else
    std::vector<char*> new_argv;
    std::vector<std::string> escaped_args;
    for (char* const* arg_ptr{argv}; *arg_ptr; ++arg_ptr) {
        std::string_view arg{*arg_ptr};
        if (arg.find_first_of(" \t\"") == std::string_view::npos) {
            // Argument has no quotes or spaces so escaping not necessary.
            new_argv.push_back(*arg_ptr);
        } else {
            // Add escaping to the command line that the executable being
            // invoked will split up using the CommandLineToArgvW function,
            // which expects arguments with spaces to be quoted, quote
            // characters to be backslash-escaped, and backslashes to also be
            // backslash-escaped, but only if they precede a quote character.
            std::string escaped{'"'}; // Start with a quote
            for (size_t i = 0; i < arg.size(); ++i) {
                if (arg[i] == '\\') {
                    // Count consecutive backslashes
                    size_t backslash_count = 0;
                    while (i < arg.size() && arg[i] == '\\') {
                        ++backslash_count;
                        ++i;
                    }
                    if (i < arg.size() && arg[i] == '"') {

Review comment (bug):
I think the following two commits should be squashed into one commit:

bde036879d `multiprocess: Add bitcoin wrapper executable`
b7213977ed `multiprocess: Add bitcoin wrapper windows support`

because otherwise there is an intermediate state where the code is broken on Windows (would try to use `execvp()` which does not exist, I guess it will not compile on Windows).

Code after:
        } 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" || arg == "help") {
            cmd.show_help = true;
        } else if (cmd.show_help && (arg == "-a" || arg == "--all")) {
            cmd.show_help_all = 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 in `wrapper_argv0`.
//!
//! @param args Command line arguments to execute, where first argument should
//!             be a relative path to a bitcoind, bitcoin-qt or other executable
//!             that will be located on the PATH or relative to wrapper_argv0.
//!
//! @param wrapper_argv0 String containing first command line argument passed to
//!                      main() to run the current executable. This is used to
//!                      help determine the path to the current executable and
//!                      how to look for new executables.
//
//! @note This function doesn't currently print anything but can be debugged
//! from the command line using strace or dtrace like:
//!
//!     strace -e trace=execve -s 10000 build/bin/bitcoin ...
//!     dtrace -n 'proc:::exec-success  /pid == $target/ { trace(curpsinfo->pr_psargs); }' -c ...
static void ExecCommand(const std::vector<const char*>& args, std::string_view wrapper_argv0)
{
    // Construct argument string for execvp
    std::vector<const char*> exec_args{args};
    exec_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)};
        exec_args[0] = exe_path_str.c_str();
        if (util::ExecVp(exec_args[0], (char*const*)exec_args.data()) == -1) {
            if (allow_notfound && errno == ENOENT) return false;
            throw std::system_error(errno, std::system_category(), strprintf("execvp failed to execute '%s'", exec_args[0]));
        }