From 28abe1bf4d8c16af92e1ef3dc1f267399f0690b9 Mon Sep 17 00:00:00 2001 From: Oliver Reiche Date: Mon, 7 Mar 2022 18:23:53 +0100 Subject: SystemCommand: Move to new module "src/buildtool/system" --- src/buildtool/execution_api/local/TARGETS | 2 +- src/buildtool/execution_api/local/local_action.cpp | 2 +- src/buildtool/file_system/TARGETS | 11 -- src/buildtool/file_system/system_command.hpp | 202 --------------------- src/buildtool/system/TARGETS | 12 ++ src/buildtool/system/system_command.hpp | 202 +++++++++++++++++++++ 6 files changed, 216 insertions(+), 215 deletions(-) delete mode 100644 src/buildtool/file_system/system_command.hpp create mode 100644 src/buildtool/system/TARGETS create mode 100644 src/buildtool/system/system_command.hpp (limited to 'src') diff --git a/src/buildtool/execution_api/local/TARGETS b/src/buildtool/execution_api/local/TARGETS index b3e54597..398f00e4 100644 --- a/src/buildtool/execution_api/local/TARGETS +++ b/src/buildtool/execution_api/local/TARGETS @@ -27,8 +27,8 @@ , ["src/buildtool/execution_api/common", "common"] , ["src/buildtool/execution_api/bazel_msg", "bazel_msg_factory"] , ["src/buildtool/file_system", "file_system_manager"] - , ["src/buildtool/file_system", "system_command"] , ["src/buildtool/file_system", "object_type"] + , ["src/buildtool/system", "system_command"] , ["src/buildtool/logging", "logging"] ] , "stage": ["src", "buildtool", "execution_api", "local"] diff --git a/src/buildtool/execution_api/local/local_action.cpp b/src/buildtool/execution_api/local/local_action.cpp index eac6ede8..37c3c085 100644 --- a/src/buildtool/execution_api/local/local_action.cpp +++ b/src/buildtool/execution_api/local/local_action.cpp @@ -8,7 +8,7 @@ #include "src/buildtool/execution_api/local/local_response.hpp" #include "src/buildtool/file_system/file_system_manager.hpp" #include "src/buildtool/file_system/object_type.hpp" -#include "src/buildtool/file_system/system_command.hpp" +#include "src/buildtool/system/system_command.hpp" namespace { diff --git a/src/buildtool/file_system/TARGETS b/src/buildtool/file_system/TARGETS index 478b5903..ef817912 100644 --- a/src/buildtool/file_system/TARGETS +++ b/src/buildtool/file_system/TARGETS @@ -15,17 +15,6 @@ ] , "stage": ["src", "buildtool", "file_system"] } -, "system_command": - { "type": ["@", "rules", "CC", "library"] - , "name": ["system_command"] - , "hdrs": ["system_command.hpp"] - , "deps": - [ "file_system_manager" - , ["src/buildtool/logging", "logging"] - , ["@", "gsl-lite", "", "gsl-lite"] - ] - , "stage": ["src", "buildtool", "file_system"] - } , "jsonfs": { "type": ["@", "rules", "CC", "library"] , "name": ["jsonfs"] diff --git a/src/buildtool/file_system/system_command.hpp b/src/buildtool/file_system/system_command.hpp deleted file mode 100644 index 66470ade..00000000 --- a/src/buildtool/file_system/system_command.hpp +++ /dev/null @@ -1,202 +0,0 @@ -#ifndef INCLUDED_SRC_BUILDTOOL_FILE_SYSTEM_EXECUTION_SYSTEM_HPP -#define INCLUDED_SRC_BUILDTOOL_FILE_SYSTEM_EXECUTION_SYSTEM_HPP - -#include -#include -#include // for strerror() -#include -#include -#include -#include -#include -#include - -#include -#include - -#include "gsl-lite/gsl-lite.hpp" -#include "src/buildtool/file_system/file_system_manager.hpp" -#include "src/buildtool/logging/logger.hpp" - -/// \brief Execute system commands and obtain stdout, stderr and return value. -/// Subsequent commands are context free and are not affected by previous -/// commands. This class is not thread-safe. -class SystemCommand { - public: - struct ExecOutput { - int return_value{}; - std::filesystem::path stdout_file{}; - std::filesystem::path stderr_file{}; - }; - - /// \brief Create execution system with name. - explicit SystemCommand(std::string name) : logger_{std::move(name)} {} - - /// \brief Execute command and arguments. - /// \param argv argv vector with the command to execute - /// \param env Environment variables set for execution. - /// \param cwd Working directory for execution. - /// \param tmpdir Temporary directory for storing stdout/stderr files. - /// \returns std::nullopt if there was an error in the execution setup - /// outside running the command itself, SystemCommand::ExecOutput otherwise. - [[nodiscard]] auto Execute(std::vector argv, - std::map env, - std::filesystem::path const& cwd, - std::filesystem::path const& tmpdir) noexcept - -> std::optional { - if (not FileSystemManager::IsDirectory(tmpdir)) { - logger_.Emit(LogLevel::Error, - "Temporary directory does not exist {}", - tmpdir.string()); - return std::nullopt; - } - - if (argv.empty()) { - logger_.Emit(LogLevel::Error, "Command cannot be empty."); - return std::nullopt; - } - - std::vector cmd = UnwrapStrings(&argv); - - std::vector env_string{}; - std::transform(std::begin(env), - std::end(env), - std::back_inserter(env_string), - [](auto& name_value) { - return name_value.first + "=" + name_value.second; - }); - std::vector envp = UnwrapStrings(&env_string); - return ExecuteCommand(cmd.data(), envp.data(), cwd, tmpdir); - } - - private: - Logger logger_; - - /// \brief Open file exclusively as write-only. - [[nodiscard]] static auto OpenFile( - std::filesystem::path const& file_path) noexcept { - static auto file_closer = [](gsl::owner f) { - if (f != nullptr) { - std::fclose(f); - } - }; - return std::unique_ptr( - std::fopen(file_path.c_str(), "wx"), file_closer); - } - - /// \brief Execute command and arguments. - /// \param cmd Command arguments as char pointer array. - /// \param envp Environment variables as char pointer array. - /// \param cwd Working directory for execution. - /// \param tmpdir Temporary directory for storing stdout/stderr files. - /// \returns ExecOutput if command was successfully submitted to the system. - /// \returns std::nullopt on internal failure. - [[nodiscard]] auto ExecuteCommand( - char* const* cmd, - char* const* envp, - std::filesystem::path const& cwd, - std::filesystem::path const& tmpdir) noexcept - -> std::optional { - auto stdout_file = tmpdir / "stdout"; - auto stderr_file = tmpdir / "stderr"; - if (auto const out = OpenFile(stdout_file)) { - if (auto const err = OpenFile(stderr_file)) { - if (auto retval = ForkAndExecute( - cmd, envp, cwd, fileno(out.get()), fileno(err.get()))) { - return ExecOutput{*retval, - std::move(stdout_file), - std::move(stderr_file)}; - } - } - else { - logger_.Emit(LogLevel::Error, - "Failed to open stderr file '{}' with error: {}", - stderr_file.string(), - strerror(errno)); - } - } - else { - logger_.Emit(LogLevel::Error, - "Failed to open stdout file '{}' with error: {}", - stdout_file.string(), - strerror(errno)); - } - - return std::nullopt; - } - - /// \brief Fork process and exec command. - /// \param cmd Command arguments as char pointer array. - /// \param envp Environment variables as char pointer array. - /// \param cwd Working directory for execution. - /// \param out_fd File descriptor to standard output file. - /// \param err_fd File descriptor to standard erro file. - /// \returns return code if command was successfully submitted to system. - /// \returns std::nullopt if fork or exec failed. - [[nodiscard]] auto ForkAndExecute(char* const* cmd, - char* const* envp, - std::filesystem::path const& cwd, - int out_fd, - int err_fd) const noexcept - -> std::optional { - // fork child process - pid_t pid = ::fork(); - if (-1 == pid) { - logger_.Emit(LogLevel::Error, - "Failed to execute '{}': cannot fork a child process.", - *cmd); - return std::nullopt; - } - - // dispatch child/parent process - if (pid == 0) { - // some executables require an open (possibly seekable) stdin, and - // therefore, we use an open temporary file that does not appear - // on the file system and will be removed automatically once the - // descriptor is closed. - gsl::owner in_file = std::tmpfile(); - auto in_fd = fileno(in_file); - - // redirect and close fds - ::dup2(in_fd, STDIN_FILENO); - ::dup2(out_fd, STDOUT_FILENO); - ::dup2(err_fd, STDERR_FILENO); - ::close(in_fd); - ::close(out_fd); - ::close(err_fd); - - [[maybe_unused]] auto anchor = - FileSystemManager::ChangeDirectory(cwd); - - // execute command in child process and exit - ::execvpe(*cmd, cmd, envp); - - // report error and terminate child process if ::execvp did not exit - logger_.Emit(LogLevel::Error, - "Failed to execute '{}' with error: {}", - *cmd, - strerror(errno)); - - std::exit(EXIT_FAILURE); - } - - // wait for child to finish and obtain return value - int status{}; - ::waitpid(pid, &status, 0); - // NOLINTNEXTLINE(hicpp-signed-bitwise) - return WEXITSTATUS(status); - } - - static auto UnwrapStrings(std::vector* v) noexcept - -> std::vector { - std::vector raw{}; - std::transform(std::begin(*v), - std::end(*v), - std::back_inserter(raw), - [](auto& str) { return str.data(); }); - raw.push_back(nullptr); - return raw; - } -}; - -#endif // INCLUDED_SRC_BUILDTOOL_FILE_SYSTEM_EXECUTION_SYSTEM_HPP diff --git a/src/buildtool/system/TARGETS b/src/buildtool/system/TARGETS new file mode 100644 index 00000000..c5db3f83 --- /dev/null +++ b/src/buildtool/system/TARGETS @@ -0,0 +1,12 @@ +{ "system_command": + { "type": ["@", "rules", "CC", "library"] + , "name": ["system_command"] + , "hdrs": ["system_command.hpp"] + , "deps": + [ ["src/buildtool/file_system", "file_system_manager"] + , ["src/buildtool/logging", "logging"] + , ["@", "gsl-lite", "", "gsl-lite"] + ] + , "stage": ["src", "buildtool", "system"] + } +} diff --git a/src/buildtool/system/system_command.hpp b/src/buildtool/system/system_command.hpp new file mode 100644 index 00000000..53c92f21 --- /dev/null +++ b/src/buildtool/system/system_command.hpp @@ -0,0 +1,202 @@ +#ifndef INCLUDED_SRC_BUILDTOOL_SYSTEM_SYSTEM_COMMAND_HPP +#define INCLUDED_SRC_BUILDTOOL_SYSTEM_SYSTEM_COMMAND_HPP + +#include +#include +#include // for strerror() +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "gsl-lite/gsl-lite.hpp" +#include "src/buildtool/file_system/file_system_manager.hpp" +#include "src/buildtool/logging/logger.hpp" + +/// \brief Execute system commands and obtain stdout, stderr and return value. +/// Subsequent commands are context free and are not affected by previous +/// commands. This class is not thread-safe. +class SystemCommand { + public: + struct ExecOutput { + int return_value{}; + std::filesystem::path stdout_file{}; + std::filesystem::path stderr_file{}; + }; + + /// \brief Create execution system with name. + explicit SystemCommand(std::string name) : logger_{std::move(name)} {} + + /// \brief Execute command and arguments. + /// \param argv argv vector with the command to execute + /// \param env Environment variables set for execution. + /// \param cwd Working directory for execution. + /// \param tmpdir Temporary directory for storing stdout/stderr files. + /// \returns std::nullopt if there was an error in the execution setup + /// outside running the command itself, SystemCommand::ExecOutput otherwise. + [[nodiscard]] auto Execute(std::vector argv, + std::map env, + std::filesystem::path const& cwd, + std::filesystem::path const& tmpdir) noexcept + -> std::optional { + if (not FileSystemManager::IsDirectory(tmpdir)) { + logger_.Emit(LogLevel::Error, + "Temporary directory does not exist {}", + tmpdir.string()); + return std::nullopt; + } + + if (argv.empty()) { + logger_.Emit(LogLevel::Error, "Command cannot be empty."); + return std::nullopt; + } + + std::vector cmd = UnwrapStrings(&argv); + + std::vector env_string{}; + std::transform(std::begin(env), + std::end(env), + std::back_inserter(env_string), + [](auto& name_value) { + return name_value.first + "=" + name_value.second; + }); + std::vector envp = UnwrapStrings(&env_string); + return ExecuteCommand(cmd.data(), envp.data(), cwd, tmpdir); + } + + private: + Logger logger_; + + /// \brief Open file exclusively as write-only. + [[nodiscard]] static auto OpenFile( + std::filesystem::path const& file_path) noexcept { + static auto file_closer = [](gsl::owner f) { + if (f != nullptr) { + std::fclose(f); + } + }; + return std::unique_ptr( + std::fopen(file_path.c_str(), "wx"), file_closer); + } + + /// \brief Execute command and arguments. + /// \param cmd Command arguments as char pointer array. + /// \param envp Environment variables as char pointer array. + /// \param cwd Working directory for execution. + /// \param tmpdir Temporary directory for storing stdout/stderr files. + /// \returns ExecOutput if command was successfully submitted to the system. + /// \returns std::nullopt on internal failure. + [[nodiscard]] auto ExecuteCommand( + char* const* cmd, + char* const* envp, + std::filesystem::path const& cwd, + std::filesystem::path const& tmpdir) noexcept + -> std::optional { + auto stdout_file = tmpdir / "stdout"; + auto stderr_file = tmpdir / "stderr"; + if (auto const out = OpenFile(stdout_file)) { + if (auto const err = OpenFile(stderr_file)) { + if (auto retval = ForkAndExecute( + cmd, envp, cwd, fileno(out.get()), fileno(err.get()))) { + return ExecOutput{*retval, + std::move(stdout_file), + std::move(stderr_file)}; + } + } + else { + logger_.Emit(LogLevel::Error, + "Failed to open stderr file '{}' with error: {}", + stderr_file.string(), + strerror(errno)); + } + } + else { + logger_.Emit(LogLevel::Error, + "Failed to open stdout file '{}' with error: {}", + stdout_file.string(), + strerror(errno)); + } + + return std::nullopt; + } + + /// \brief Fork process and exec command. + /// \param cmd Command arguments as char pointer array. + /// \param envp Environment variables as char pointer array. + /// \param cwd Working directory for execution. + /// \param out_fd File descriptor to standard output file. + /// \param err_fd File descriptor to standard erro file. + /// \returns return code if command was successfully submitted to system. + /// \returns std::nullopt if fork or exec failed. + [[nodiscard]] auto ForkAndExecute(char* const* cmd, + char* const* envp, + std::filesystem::path const& cwd, + int out_fd, + int err_fd) const noexcept + -> std::optional { + // fork child process + pid_t pid = ::fork(); + if (-1 == pid) { + logger_.Emit(LogLevel::Error, + "Failed to execute '{}': cannot fork a child process.", + *cmd); + return std::nullopt; + } + + // dispatch child/parent process + if (pid == 0) { + // some executables require an open (possibly seekable) stdin, and + // therefore, we use an open temporary file that does not appear + // on the file system and will be removed automatically once the + // descriptor is closed. + gsl::owner in_file = std::tmpfile(); + auto in_fd = fileno(in_file); + + // redirect and close fds + ::dup2(in_fd, STDIN_FILENO); + ::dup2(out_fd, STDOUT_FILENO); + ::dup2(err_fd, STDERR_FILENO); + ::close(in_fd); + ::close(out_fd); + ::close(err_fd); + + [[maybe_unused]] auto anchor = + FileSystemManager::ChangeDirectory(cwd); + + // execute command in child process and exit + ::execvpe(*cmd, cmd, envp); + + // report error and terminate child process if ::execvp did not exit + logger_.Emit(LogLevel::Error, + "Failed to execute '{}' with error: {}", + *cmd, + strerror(errno)); + + std::exit(EXIT_FAILURE); + } + + // wait for child to finish and obtain return value + int status{}; + ::waitpid(pid, &status, 0); + // NOLINTNEXTLINE(hicpp-signed-bitwise) + return WEXITSTATUS(status); + } + + static auto UnwrapStrings(std::vector* v) noexcept + -> std::vector { + std::vector raw{}; + std::transform(std::begin(*v), + std::end(*v), + std::back_inserter(raw), + [](auto& str) { return str.data(); }); + raw.push_back(nullptr); + return raw; + } +}; + +#endif // INCLUDED_SRC_BUILDTOOL_SYSTEM_SYSTEM_COMMAND_HPP -- cgit v1.2.3