diff --git a/rust/private/rust.bzl b/rust/private/rust.bzl index 0cc569ecaf..f0903177da 100644 --- a/rust/private/rust.bzl +++ b/rust/private/rust.bzl @@ -1094,12 +1094,12 @@ rust_binary = rule( """), ) -def _common_attrs_for_binary_without_process_wrapper(attrs): +def _common_attrs_for_binary_with_basic_process_wrapper(attrs): new_attr = dict(attrs) # use a fake process wrapper new_attr["_process_wrapper"] = attr.label( - default = None, + default = Label("//util/process_wrapper:basic_process_wrapper"), executable = True, allow_single_file = True, cfg = "exec", @@ -1108,7 +1108,7 @@ def _common_attrs_for_binary_without_process_wrapper(attrs): # fix stamp = 0 new_attr["stamp"] = attr.int( doc = dedent("""\ - Fix `stamp = 0` as stamping is not supported when building without process_wrapper: + Fix `stamp = 0` as stamping is not supported when building with the basic process_wrapper: https://github.com/bazelbuild/rules_rust/blob/8df4517d370b0c543a01ba38b63e1d5a4104b035/rust/private/rustc.bzl#L955 """), default = 0, @@ -1120,10 +1120,10 @@ def _common_attrs_for_binary_without_process_wrapper(attrs): # Provides an internal rust_{binary,library} to use that we can use to build the process # wrapper, this breaks the dependency of rust_* on the process wrapper by # setting it to None, which the functions in rustc detect and build accordingly. -rust_binary_without_process_wrapper = rule( +rust_binary_with_basic_process_wrapper = rule( implementation = _rust_binary_impl, provides = _common_providers, - attrs = _common_attrs_for_binary_without_process_wrapper(_common_attrs.items() + _rust_binary_attrs.items()), + attrs = _common_attrs_for_binary_with_basic_process_wrapper(_common_attrs.items() + _rust_binary_attrs.items()), executable = True, fragments = ["cpp"], host_fragments = ["cpp"], @@ -1134,10 +1134,10 @@ rust_binary_without_process_wrapper = rule( incompatible_use_toolchain_transition = True, ) -rust_library_without_process_wrapper = rule( +rust_library_with_basic_process_wrapper = rule( implementation = _rust_library_impl, provides = _common_providers, - attrs = dict(_common_attrs_for_binary_without_process_wrapper(_common_attrs).items()), + attrs = dict(_common_attrs_for_binary_with_basic_process_wrapper(_common_attrs).items()), fragments = ["cpp"], host_fragments = ["cpp"], toolchains = [ diff --git a/rust/private/rustc.bzl b/rust/private/rustc.bzl index 2e8e82ca5b..1ae6a1c03a 100644 --- a/rust/private/rustc.bzl +++ b/rust/private/rustc.bzl @@ -35,6 +35,7 @@ load( "is_exec_configuration", "make_static_lib_symlink", "relativize", + "uses_process_wrapper", ) BuildInfo = _BuildInfo @@ -1207,7 +1208,7 @@ def rustc_compile_action( dsym_folder = ctx.actions.declare_directory(crate_info.output.basename + ".dSYM", sibling = crate_info.output) action_outputs.append(dsym_folder) - if ctx.executable._process_wrapper: + if uses_process_wrapper(ctx): # Run as normal ctx.actions.run( executable = ctx.executable._process_wrapper, @@ -1239,17 +1240,17 @@ def rustc_compile_action( ), ) else: - # Run without process_wrapper + # Run with the basic process_wrapper if build_env_files or build_flags_files or stamp or build_metadata: - fail("build_env_files, build_flags_files, stamp, build_metadata are not supported when building without process_wrapper") + fail("build_env_files, build_flags_files, stamp, build_metadata are not supported when building with the basic process wrapper") ctx.actions.run( - executable = toolchain.rustc, + executable = ctx.executable._process_wrapper, inputs = compile_inputs, outputs = action_outputs, env = env, - arguments = [args.rustc_flags], + arguments = [toolchain.rustc.path, args.rustc_flags], mnemonic = "Rustc", - progress_message = "Compiling Rust (without process_wrapper) {} {}{} ({} files)".format( + progress_message = "Compiling Rust (for process wrapper) {} {}{} ({} files)".format( crate_info.type, ctx.label.name, formatted_version, diff --git a/rust/private/utils.bzl b/rust/private/utils.bzl index 6cb8b0691c..90da62d02c 100644 --- a/rust/private/utils.bzl +++ b/rust/private/utils.bzl @@ -680,7 +680,7 @@ def can_build_metadata(toolchain, ctx, crate_type): # 4) the crate_type is rlib or lib. return toolchain._pipelined_compilation and \ toolchain.exec_triple.system != "windows" and \ - ctx.attr._process_wrapper and \ + uses_process_wrapper(ctx) and \ crate_type in ("rlib", "lib") def crate_root_src(name, srcs, crate_type): @@ -708,6 +708,18 @@ def crate_root_src(name, srcs, crate_type): fail("No {} source file found.".format(" or ".join(file_names)), "srcs") return crate_root +def uses_process_wrapper(ctx): + """Returns true if this action uses the full fledged process wrapper. + + Args: + ctx (list): The rule's context object + + Returns: + bool: True if the full flaged process wrapper is used, false otherwise. + """ + + return "basic_process_wrapper" not in ctx.executable._process_wrapper.path + def _shortest_src_with_basename(srcs, basename): """Finds the shortest among the paths in srcs that match the desired basename. diff --git a/util/process_wrapper/BUILD.bazel b/util/process_wrapper/BUILD.bazel index c5276dd9cb..ffbceb9612 100644 --- a/util/process_wrapper/BUILD.bazel +++ b/util/process_wrapper/BUILD.bazel @@ -1,9 +1,10 @@ +load("@rules_cc//cc:defs.bzl", "cc_binary") load("//rust:defs.bzl", "rust_test") # buildifier: disable=bzl-visibility -load("//rust/private:rust.bzl", "rust_binary_without_process_wrapper") +load("//rust/private:rust.bzl", "rust_binary_with_basic_process_wrapper") -rust_binary_without_process_wrapper( +rust_binary_with_basic_process_wrapper( name = "process_wrapper", srcs = glob(["*.rs"]), edition = "2018", @@ -18,3 +19,26 @@ rust_test( crate = ":process_wrapper", edition = "2018", ) + +cc_binary( + name = "basic_process_wrapper", + srcs = [ + "basic_process_wrapper.cc", + "system.h", + ] + select({ + "@platforms//os:windows": [ + "system_windows.cc", + ], + "//conditions:default": [ + "system_posix.cc", + ], + }), + defines = [] + select({ + "@platforms//os:windows": [ + "UNICODE", + "_UNICODE", + ], + "//conditions:default": [], + }), + visibility = ["//visibility:public"], +) diff --git a/util/process_wrapper/BUILD.tinyjson.bazel b/util/process_wrapper/BUILD.tinyjson.bazel index 31f9da24f3..7cbd06d656 100644 --- a/util/process_wrapper/BUILD.tinyjson.bazel +++ b/util/process_wrapper/BUILD.tinyjson.bazel @@ -1,7 +1,7 @@ # buildifier: disable=bzl-visibility -load("@rules_rust//rust/private:rust.bzl", "rust_library_without_process_wrapper") +load("@rules_rust//rust/private:rust.bzl", "rust_library_with_basic_process_wrapper") -rust_library_without_process_wrapper( +rust_library_with_basic_process_wrapper( name = "tinyjson", srcs = glob(["src/*.rs"]), edition = "2018", diff --git a/util/process_wrapper/basic_process_wrapper.cc b/util/process_wrapper/basic_process_wrapper.cc new file mode 100644 index 0000000000..8563956386 --- /dev/null +++ b/util/process_wrapper/basic_process_wrapper.cc @@ -0,0 +1,37 @@ +#include +#include +#include +#include +#include + +#include "util/process_wrapper/system.h" + +using CharType = process_wrapper::System::StrType::value_type; + +// A basic process wrapper whose only purpose is to preserve determinism when +// building the true process wrapper. We do this by passing --remap-path-prefix=$pwd= +// to the command line. +int PW_MAIN(int argc, const CharType* argv[], const CharType* envp[]) { + using namespace process_wrapper; + + System::EnvironmentBlock environment_block; + + // Taking all environment variables from the current process + // and sending them down to the child process + for (int i = 0; envp[i] != nullptr; ++i) { + environment_block.push_back(envp[i]); + } + + System::StrType exec_path = argv[1]; + + System::Arguments arguments; + + for (int i = 2; i < argc; ++i) { + arguments.push_back(argv[i]); + } + System::StrType pwd_prefix = + PW_SYS_STR("--remap-path-prefix=") + System::GetWorkingDirectory() + PW_SYS_STR("="); + arguments.push_back(pwd_prefix); + + return System::Exec(exec_path, arguments, environment_block); +} diff --git a/util/process_wrapper/system.h b/util/process_wrapper/system.h new file mode 100644 index 0000000000..53fcc2436b --- /dev/null +++ b/util/process_wrapper/system.h @@ -0,0 +1,47 @@ +#ifndef LIB_PROCESS_WRAPPER_SYSTEM_H_ +#define LIB_PROCESS_WRAPPER_SYSTEM_H_ + +#include +#include + +#if defined(_WIN32) && defined(UNICODE) +#define PW_WIN_UNICODE +#endif // defined(_WIN32) && defined(UNICODE) + +#if defined(PW_WIN_UNICODE) +#define PW_SYS_STR(str) L##str +#define PW_MAIN wmain +#else +#define PW_SYS_STR(str) str +#define PW_MAIN main +#endif + +namespace process_wrapper { + +class System { + public: +#if defined(PW_WIN_UNICODE) + using StrType = std::wstring; +#else + using StrType = std::string; +#endif // defined(PW_WIN_UNICODE) + + using StrVecType = std::vector; + using Arguments = StrVecType; + using EnvironmentBlock = StrVecType; + + public: + // Gets the working directory of the current process + static StrType GetWorkingDirectory(); + + // Simple function to execute a process that inherits all the current + // process handles. + // Even if the function doesn't modify global state it is not reentrant + // It is meant to be called once during the lifetime of the parent process + static int Exec(const StrType& executable, const Arguments& arguments, + const EnvironmentBlock& environment_block); +}; + +} // namespace process_wrapper + +#endif // LIB_PROCESS_WRAPPER_SYSTEM_H_ diff --git a/util/process_wrapper/system_posix.cc b/util/process_wrapper/system_posix.cc new file mode 100644 index 0000000000..0ded298ba3 --- /dev/null +++ b/util/process_wrapper/system_posix.cc @@ -0,0 +1,127 @@ +#include "util/process_wrapper/system.h" + +// posix headers +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +namespace process_wrapper { + +namespace { + +class OutputPipe { + public: + static constexpr size_t kReadEndDesc = 0; + static constexpr size_t kWriteEndDesc = 1; + + ~OutputPipe() { + CloseReadEnd(); + CloseWriteEnd(); + } + + int CreateEnds() { + if (pipe(output_pipe_desc_) != 0) { + std::cerr << "process wrapper error: failed to open the stdout pipes.\n"; + return false; + } + return true; + } + void DupWriteEnd(int newfd) { + dup2(output_pipe_desc_[kWriteEndDesc], newfd); + CloseReadEnd(); + CloseWriteEnd(); + } + + void CloseReadEnd() { Close(kReadEndDesc); } + void CloseWriteEnd() { Close(kWriteEndDesc); } + + int ReadEndDesc() const { return output_pipe_desc_[kReadEndDesc]; } + int WriteEndDesc() const { return output_pipe_desc_[kWriteEndDesc]; } + + private: + void Close(size_t idx) { + if (output_pipe_desc_[idx] > 0) { + close(output_pipe_desc_[idx]); + } + output_pipe_desc_[idx] = -1; + } + int output_pipe_desc_[2] = {-1}; +}; + +} // namespace + +System::StrType System::GetWorkingDirectory() { + const size_t kMaxBufferLength = 4096; + char cwd[kMaxBufferLength]; + if (getcwd(cwd, sizeof(cwd)) == NULL) { + return System::StrType{}; + } + return System::StrType{cwd}; +} + +int System::Exec(const System::StrType &executable, + const System::Arguments &arguments, + const System::EnvironmentBlock &environment_block) { + OutputPipe stdout_pipe; + if (!stdout_pipe.CreateEnds()) { + return -1; + } + OutputPipe stderr_pipe; + if (!stderr_pipe.CreateEnds()) { + return -1; + } + + pid_t child_pid = fork(); + if (child_pid < 0) { + std::cerr << "process wrapper error: failed to fork the current process: " + << std::strerror(errno) << ".\n"; + return -1; + } else if (child_pid == 0) { + std::vector argv; + argv.push_back(const_cast(executable.c_str())); + for (const StrType &argument : arguments) { + argv.push_back(const_cast(argument.c_str())); + } + argv.push_back(nullptr); + + std::vector envp; + for (const StrType &ev : environment_block) { + envp.push_back(const_cast(ev.c_str())); + } + envp.push_back(nullptr); + + umask(022); + execve(executable.c_str(), argv.data(), envp.data()); + std::cerr << "process wrapper error: failed to exec the new process: " + << std::strerror(errno) << ".\n"; + return -1; + } + + int err, exit_status; + do { + err = waitpid(child_pid, &exit_status, 0); + } while (err == -1 && errno == EINTR); + + if (WIFEXITED(exit_status)) { + return WEXITSTATUS(exit_status); + } else if (WIFSIGNALED(exit_status)) { + raise(WTERMSIG(exit_status)); + } else if (WIFSTOPPED(exit_status)) { + raise(WSTOPSIG(exit_status)); + } else { + std::cerr << "process wrapper error: failed to parse exit code of the " + "child process: " + << exit_status << ".\n"; + } + return -1; +} + +} // namespace process_wrapper diff --git a/util/process_wrapper/system_windows.cc b/util/process_wrapper/system_windows.cc new file mode 100644 index 0000000000..df3d4d1c51 --- /dev/null +++ b/util/process_wrapper/system_windows.cc @@ -0,0 +1,189 @@ +#include + +#include "util/process_wrapper/system.h" + +#ifndef WIN32_LEAN_AND_MEAN +#define WIN32_LEAN_AND_MEAN +#endif + +#include + +#include + +namespace process_wrapper { + +namespace { + +// We need to follow specific quoting rules for maximum compatibility as +// explained here: +// https://docs.microsoft.com/en-us/archive/blogs/twistylittlepassagesallalike/everyone-quotes-command-line-arguments-the-wrong-way +void ArgumentQuote(const System::StrType& argument, + System::StrType& command_line) { + if (argument.empty() == false && + argument.find_first_of(PW_SYS_STR(" \t\n\v\"")) == argument.npos) { + command_line.append(argument); + } else { + command_line.push_back(PW_SYS_STR('"')); + + for (auto it = argument.begin();; ++it) { + unsigned number_backslashes = 0; + + while (it != argument.end() && *it == PW_SYS_STR('\\')) { + ++it; + ++number_backslashes; + } + + if (it == argument.end()) { + command_line.append(number_backslashes * 2, PW_SYS_STR('\\')); + break; + } else if (*it == L'"') { + command_line.append(number_backslashes * 2 + 1, PW_SYS_STR('\\')); + command_line.push_back(*it); + } else { + command_line.append(number_backslashes, PW_SYS_STR('\\')); + command_line.push_back(*it); + } + } + command_line.push_back(PW_SYS_STR('"')); + } +} + +// Arguments needs to be quoted and space separated +void MakeCommandLine(const System::Arguments& arguments, + System::StrType& command_line) { + for (const System::StrType& argument : arguments) { + command_line.push_back(PW_SYS_STR(' ')); + ArgumentQuote(argument, command_line); + } +} + +// Environment variables are \0 separated +void MakeEnvironmentBlock(const System::EnvironmentBlock& environment_block, + System::StrType& environment_block_win) { + for (const System::StrType& ev : environment_block) { + environment_block_win += ev; + environment_block_win.push_back(PW_SYS_STR('\0')); + } + environment_block_win.push_back(PW_SYS_STR('\0')); +} + +std::string GetLastErrorAsStr() { + LPVOID msg_buffer = nullptr; + size_t size = ::FormatMessageA( + FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | + FORMAT_MESSAGE_IGNORE_INSERTS, + NULL, ::GetLastError(), MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), + (LPSTR)&msg_buffer, 0, NULL); + std::string error((LPSTR)msg_buffer, size); + LocalFree(msg_buffer); + return error; +} + +class OutputPipe { + public: + static constexpr size_t kReadEndHandle = 0; + static constexpr size_t kWriteEndHandle = 1; + + ~OutputPipe() { + CloseReadEnd(); + CloseWriteEnd(); + } + + bool CreateEnds(SECURITY_ATTRIBUTES& saAttr) { + if (!::CreatePipe(&output_pipe_handles_[kReadEndHandle], + &output_pipe_handles_[kWriteEndHandle], &saAttr, 0)) { + return false; + } + + if (!::SetHandleInformation(output_pipe_handles_[kReadEndHandle], + HANDLE_FLAG_INHERIT, 0)) { + return false; + } + + return true; + } + + void CloseReadEnd() { Close(kReadEndHandle); } + void CloseWriteEnd() { Close(kWriteEndHandle); } + + HANDLE ReadEndHandle() const { return output_pipe_handles_[kReadEndHandle]; } + HANDLE WriteEndHandle() const { + return output_pipe_handles_[kWriteEndHandle]; + } + + + private: + void Close(size_t idx) { + if (output_pipe_handles_[idx] != nullptr) { + ::CloseHandle(output_pipe_handles_[idx]); + } + output_pipe_handles_[idx] = nullptr; + } + HANDLE output_pipe_handles_[2] = {nullptr}; +}; + +} // namespace + +System::StrType System::GetWorkingDirectory() { + constexpr DWORD kMaxBufferLength = 4096; + TCHAR buffer[kMaxBufferLength]; + if (::GetCurrentDirectory(kMaxBufferLength, buffer) == 0) { + return System::StrType{}; + } + return System::StrType{buffer}; +} + +int System::Exec(const System::StrType& executable, + const System::Arguments& arguments, + const System::EnvironmentBlock& environment_block) { + STARTUPINFO startup_info; + ZeroMemory(&startup_info, sizeof(STARTUPINFO)); + startup_info.cb = sizeof(STARTUPINFO); + + OutputPipe stdout_pipe; + OutputPipe stderr_pipe; + + System::StrType command_line; + ArgumentQuote(executable, command_line); + MakeCommandLine(arguments, command_line); + + System::StrType environment_block_win; + MakeEnvironmentBlock(environment_block, environment_block_win); + + PROCESS_INFORMATION process_info; + ZeroMemory(&process_info, sizeof(PROCESS_INFORMATION)); + + BOOL success = ::CreateProcess( + /*lpApplicationName*/ nullptr, + /*lpCommandLine*/ command_line.empty() ? nullptr : &command_line[0], + /*lpProcessAttributes*/ nullptr, + /*lpThreadAttributes*/ nullptr, + /*bInheritHandles*/ TRUE, + /*dwCreationFlags*/ 0 +#if defined(UNICODE) + | CREATE_UNICODE_ENVIRONMENT +#endif // defined(UNICODE) + , + /*lpEnvironment*/ environment_block_win.empty() + ? nullptr + : &environment_block_win[0], + /*lpCurrentDirectory*/ nullptr, + /*lpStartupInfo*/ &startup_info, + /*lpProcessInformation*/ &process_info); + + if (success == FALSE) { + std::cerr << "process wrapper error: failed to launch a new process: " + << GetLastErrorAsStr(); + return -1; + } + + DWORD exit_status; + WaitForSingleObject(process_info.hProcess, INFINITE); + if (GetExitCodeProcess(process_info.hProcess, &exit_status) == FALSE) + exit_status = -1; + CloseHandle(process_info.hThread); + CloseHandle(process_info.hProcess); + return exit_status; +} + +} // namespace process_wrapper