diff --git a/src/util/CMakeLists.txt b/src/util/CMakeLists.txt index 4999dbf13f0..0ae9a08eebb 100644 --- a/src/util/CMakeLists.txt +++ b/src/util/CMakeLists.txt @@ -9,6 +9,7 @@ add_library(bitcoin_util STATIC EXCLUDE_FROM_ALL bytevectorhash.cpp chaintype.cpp check.cpp + exec.cpp exception.cpp feefrac.cpp fs.cpp diff --git a/src/util/exec.cpp b/src/util/exec.cpp new file mode 100644 index 00000000000..0f13d1d4af3 --- /dev/null +++ b/src/util/exec.cpp @@ -0,0 +1,75 @@ +// Copyright (c) 2025 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 + +#include +#include + +#include +#include + +#ifdef WIN32 +#include +#include +#else +#include +#endif + +namespace util { +int ExecVp(const char* file, char* const argv[]) +{ +#ifndef WIN32 + return execvp(file, argv); +#else + std::vector escaped_args; + std::wstring_convert> converter; + for (char* const* arg_ptr{argv}; *arg_ptr; ++arg_ptr) { + subprocess::util::quote_argument(converter.from_bytes(*arg_ptr), escaped_args.emplace_back(), false); + } + + std::vector new_argv; + new_argv.reserve(escaped_args.size() + 1); + for (const auto& s : escaped_args) new_argv.push_back(s.c_str()); + new_argv.push_back(nullptr); + return _wexecvp(converter.from_bytes(file).c_str(), new_argv.data()); +#endif +} + +fs::path GetExePath(std::string_view argv0) +{ + // Try to figure out where executable is located. This does a simplified + // search that won't work perfectly on every platform and doesn't need to, + // as it is only currently being used in a convenience wrapper binary to try + // to prioritize locally built or installed executables over system + // executables. + const fs::path argv0_path{fs::PathFromString(std::string{argv0})}; + fs::path path{argv0_path}; + std::error_code ec; +#ifndef WIN32 + // If argv0 doesn't contain a path separator, it was invoked from the system + // PATH and can be searched for there. + if (!argv0_path.has_parent_path()) { + 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::is_regular_file(candidate, ec)) { + path = candidate; + break; + } + } + } + } +#else + wchar_t module_path[MAX_PATH]; + if (GetModuleFileNameW(nullptr, module_path, MAX_PATH) > 0) { + path = fs::path{module_path}; + } +#endif + return path; +} + +} // namespace util diff --git a/src/util/exec.h b/src/util/exec.h new file mode 100644 index 00000000000..4c207265fa0 --- /dev/null +++ b/src/util/exec.h @@ -0,0 +1,23 @@ +// Copyright (c) 2025 The Bitcoin Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#ifndef BITCOIN_UTIL_EXEC_H +#define BITCOIN_UTIL_EXEC_H + +#include + +#include + +namespace util { +//! Cross-platform wrapper for POSIX execvp function. +//! Arguments and return value are the same as for POSIX execvp, and the argv +//! array should consist of null terminated strings and be null terminated +//! itself, like the POSIX function. +int ExecVp(const char* file, char* const argv[]); +//! Return path to current executable assuming it was invoked with argv0. +//! If path could not be determined, returns an empty path. +fs::path GetExePath(std::string_view argv0); +} // namespace util + +#endif // BITCOIN_UTIL_EXEC_H