| // Protocol Buffers - Google's data interchange format |
| // Copyright 2023 Google LLC. All rights reserved. |
| // https://developers.google.com/protocol-buffers/ |
| // |
| // Redistribution and use in source and binary forms, with or without |
| // modification, are permitted provided that the following conditions are |
| // met: |
| // |
| // * Redistributions of source code must retain the above copyright |
| // notice, this list of conditions and the following disclaimer. |
| // * Redistributions in binary form must reproduce the above |
| // copyright notice, this list of conditions and the following disclaimer |
| // in the documentation and/or other materials provided with the |
| // distribution. |
| // * Neither the name of Google LLC nor the names of its |
| // contributors may be used to endorse or promote products derived from |
| // this software without specific prior written permission. |
| // |
| // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
| // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
| // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
| // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT |
| // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
| // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
| // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
| // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
| // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
| // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
| // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
| |
| // Shamelessly copied from the protobuf compiler's subprocess.cc |
| // except this version passes strings instead of Messages. |
| |
| #include "upb_generator/subprocess.h" |
| |
| #include <algorithm> |
| #include <cstring> |
| #include <iostream> |
| |
| #ifndef _MSVC_LANG |
| #include <errno.h> |
| #include <signal.h> |
| #include <sys/select.h> |
| #include <sys/wait.h> |
| #endif |
| |
| #include "absl/log/absl_log.h" |
| #include "absl/strings/substitute.h" |
| |
| // Must be last. |
| #include "upb/port/def.inc" |
| |
| namespace upb { |
| namespace generator { |
| |
| namespace { |
| char* portable_strdup(const char* s) { |
| char* ns = (char*)malloc(strlen(s) + 1); |
| if (ns != nullptr) { |
| strcpy(ns, s); |
| } |
| return ns; |
| } |
| } // namespace |
| |
| #ifdef _WIN32 |
| |
| static void CloseHandleOrDie(HANDLE handle) { |
| if (!CloseHandle(handle)) { |
| ABSL_LOG(FATAL) << "CloseHandle: " |
| << Subprocess::Win32ErrorMessage(GetLastError()); |
| } |
| } |
| |
| Subprocess::Subprocess() |
| : process_start_error_(ERROR_SUCCESS), |
| child_handle_(nullptr), |
| child_stdin_(nullptr), |
| child_stdout_(nullptr) {} |
| |
| Subprocess::~Subprocess() { |
| if (child_stdin_ != nullptr) { |
| CloseHandleOrDie(child_stdin_); |
| } |
| if (child_stdout_ != nullptr) { |
| CloseHandleOrDie(child_stdout_); |
| } |
| } |
| |
| void Subprocess::Start(const std::string& program, SearchMode search_mode) { |
| // Create the pipes. |
| HANDLE stdin_pipe_read; |
| HANDLE stdin_pipe_write; |
| HANDLE stdout_pipe_read; |
| HANDLE stdout_pipe_write; |
| |
| if (!CreatePipe(&stdin_pipe_read, &stdin_pipe_write, nullptr, 0)) { |
| ABSL_LOG(FATAL) << "CreatePipe: " << Win32ErrorMessage(GetLastError()); |
| } |
| if (!CreatePipe(&stdout_pipe_read, &stdout_pipe_write, nullptr, 0)) { |
| ABSL_LOG(FATAL) << "CreatePipe: " << Win32ErrorMessage(GetLastError()); |
| } |
| |
| // Make child side of the pipes inheritable. |
| if (!SetHandleInformation(stdin_pipe_read, HANDLE_FLAG_INHERIT, |
| HANDLE_FLAG_INHERIT)) { |
| ABSL_LOG(FATAL) << "SetHandleInformation: " |
| << Win32ErrorMessage(GetLastError()); |
| } |
| if (!SetHandleInformation(stdout_pipe_write, HANDLE_FLAG_INHERIT, |
| HANDLE_FLAG_INHERIT)) { |
| ABSL_LOG(FATAL) << "SetHandleInformation: " |
| << Win32ErrorMessage(GetLastError()); |
| } |
| |
| // Setup STARTUPINFO to redirect handles. |
| STARTUPINFOA startup_info; |
| ZeroMemory(&startup_info, sizeof(startup_info)); |
| startup_info.cb = sizeof(startup_info); |
| startup_info.dwFlags = STARTF_USESTDHANDLES; |
| startup_info.hStdInput = stdin_pipe_read; |
| startup_info.hStdOutput = stdout_pipe_write; |
| startup_info.hStdError = GetStdHandle(STD_ERROR_HANDLE); |
| |
| if (startup_info.hStdError == INVALID_HANDLE_VALUE) { |
| ABSL_LOG(FATAL) << "GetStdHandle: " << Win32ErrorMessage(GetLastError()); |
| } |
| |
| // Invoking cmd.exe allows for '.bat' files from the path as well as '.exe'. |
| // Using a malloc'ed string because CreateProcess() can mutate its second |
| // parameter. |
| char* command_line = |
| portable_strdup(("cmd.exe /c \"" + program + "\"").c_str()); |
| |
| // Create the process. |
| PROCESS_INFORMATION process_info; |
| |
| if (CreateProcessA((search_mode == SEARCH_PATH) ? nullptr : program.c_str(), |
| (search_mode == SEARCH_PATH) ? command_line : nullptr, |
| nullptr, // process security attributes |
| nullptr, // thread security attributes |
| TRUE, // inherit handles? |
| 0, // obscure creation flags |
| nullptr, // environment (inherit from parent) |
| nullptr, // current directory (inherit from parent) |
| &startup_info, &process_info)) { |
| child_handle_ = process_info.hProcess; |
| CloseHandleOrDie(process_info.hThread); |
| child_stdin_ = stdin_pipe_write; |
| child_stdout_ = stdout_pipe_read; |
| } else { |
| process_start_error_ = GetLastError(); |
| CloseHandleOrDie(stdin_pipe_write); |
| CloseHandleOrDie(stdout_pipe_read); |
| } |
| |
| CloseHandleOrDie(stdin_pipe_read); |
| CloseHandleOrDie(stdout_pipe_write); |
| free(command_line); |
| } |
| |
| bool Subprocess::Communicate(const std::string& input_data, |
| std::string* output_data, std::string* error) { |
| if (process_start_error_ != ERROR_SUCCESS) { |
| *error = Win32ErrorMessage(process_start_error_); |
| return false; |
| } |
| |
| GOOGLE_CHECK(child_handle_ != nullptr) << "Must call Start() first."; |
| |
| int input_pos = 0; |
| |
| while (child_stdout_ != nullptr) { |
| HANDLE handles[2]; |
| int handle_count = 0; |
| |
| if (child_stdin_ != nullptr) { |
| handles[handle_count++] = child_stdin_; |
| } |
| if (child_stdout_ != nullptr) { |
| handles[handle_count++] = child_stdout_; |
| } |
| |
| DWORD wait_result = |
| WaitForMultipleObjects(handle_count, handles, FALSE, INFINITE); |
| |
| HANDLE signaled_handle = nullptr; |
| if (wait_result >= WAIT_OBJECT_0 && |
| wait_result < WAIT_OBJECT_0 + handle_count) { |
| signaled_handle = handles[wait_result - WAIT_OBJECT_0]; |
| } else if (wait_result == WAIT_FAILED) { |
| ABSL_LOG(FATAL) << "WaitForMultipleObjects: " |
| << Win32ErrorMessage(GetLastError()); |
| } else { |
| ABSL_LOG(FATAL) << "WaitForMultipleObjects: Unexpected return code: " |
| << wait_result; |
| } |
| |
| if (signaled_handle == child_stdin_) { |
| DWORD n; |
| if (!WriteFile(child_stdin_, input_data.data() + input_pos, |
| input_data.size() - input_pos, &n, nullptr)) { |
| // Child closed pipe. Presumably it will report an error later. |
| // Pretend we're done for now. |
| input_pos = input_data.size(); |
| } else { |
| input_pos += n; |
| } |
| |
| if (input_pos == input_data.size()) { |
| // We're done writing. Close. |
| CloseHandleOrDie(child_stdin_); |
| child_stdin_ = nullptr; |
| } |
| } else if (signaled_handle == child_stdout_) { |
| char buffer[4096]; |
| DWORD n; |
| |
| if (!ReadFile(child_stdout_, buffer, sizeof(buffer), &n, nullptr)) { |
| // We're done reading. Close. |
| CloseHandleOrDie(child_stdout_); |
| child_stdout_ = nullptr; |
| } else { |
| output_data->append(buffer, n); |
| } |
| } |
| } |
| |
| if (child_stdin_ != nullptr) { |
| // Child did not finish reading input before it closed the output. |
| // Presumably it exited with an error. |
| CloseHandleOrDie(child_stdin_); |
| child_stdin_ = nullptr; |
| } |
| |
| DWORD wait_result = WaitForSingleObject(child_handle_, INFINITE); |
| |
| if (wait_result == WAIT_FAILED) { |
| ABSL_LOG(FATAL) << "WaitForSingleObject: " |
| << Win32ErrorMessage(GetLastError()); |
| } else if (wait_result != WAIT_OBJECT_0) { |
| ABSL_LOG(FATAL) << "WaitForSingleObject: Unexpected return code: " |
| << wait_result; |
| } |
| |
| DWORD exit_code; |
| if (!GetExitCodeProcess(child_handle_, &exit_code)) { |
| ABSL_LOG(FATAL) << "GetExitCodeProcess: " |
| << Win32ErrorMessage(GetLastError()); |
| } |
| |
| CloseHandleOrDie(child_handle_); |
| child_handle_ = nullptr; |
| |
| if (exit_code != 0) { |
| *error = absl::Substitute("Plugin failed with status code $0.", exit_code); |
| return false; |
| } |
| |
| return true; |
| } |
| |
| std::string Subprocess::Win32ErrorMessage(DWORD error_code) { |
| char* message; |
| |
| // WTF? |
| FormatMessageA(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | |
| FORMAT_MESSAGE_IGNORE_INSERTS, |
| nullptr, error_code, |
| MAKELANGID(LANG_ENGLISH, SUBLANG_ENGLISH_US), |
| (LPSTR)&message, // NOT A BUG! |
| 0, nullptr); |
| |
| std::string result = message; |
| LocalFree(message); |
| return result; |
| } |
| |
| // =================================================================== |
| |
| #else // _WIN32 |
| |
| Subprocess::Subprocess() |
| : child_pid_(-1), child_stdin_(-1), child_stdout_(-1) {} |
| |
| Subprocess::~Subprocess() { |
| if (child_stdin_ != -1) { |
| close(child_stdin_); |
| } |
| if (child_stdout_ != -1) { |
| close(child_stdout_); |
| } |
| } |
| |
| void Subprocess::Start(const std::string& program, SearchMode search_mode) { |
| // Note that we assume that there are no other threads, thus we don't have to |
| // do crazy stuff like using socket pairs or avoiding libc locks. |
| |
| // [0] is read end, [1] is write end. |
| int stdin_pipe[2]; |
| int stdout_pipe[2]; |
| |
| int p0 = pipe(stdin_pipe); |
| int p1 = pipe(stdout_pipe); |
| UPB_ASSERT(p0 != -1); |
| UPB_ASSERT(p1 != -1); |
| |
| char* argv[2] = {portable_strdup(program.c_str()), nullptr}; |
| |
| child_pid_ = fork(); |
| if (child_pid_ == -1) { |
| std::cerr << "fork: " << strerror(errno); |
| } else if (child_pid_ == 0) { |
| // We are the child. |
| dup2(stdin_pipe[0], STDIN_FILENO); |
| dup2(stdout_pipe[1], STDOUT_FILENO); |
| |
| close(stdin_pipe[0]); |
| close(stdin_pipe[1]); |
| close(stdout_pipe[0]); |
| close(stdout_pipe[1]); |
| |
| switch (search_mode) { |
| case SEARCH_PATH: |
| execvp(argv[0], argv); |
| break; |
| case EXACT_NAME: |
| execv(argv[0], argv); |
| break; |
| } |
| |
| // Write directly to STDERR_FILENO to avoid stdio code paths that may do |
| // stuff that is unsafe here. |
| int ignored; |
| ignored = write(STDERR_FILENO, argv[0], strlen(argv[0])); |
| const char* message = |
| ": program not found or is not executable\n" |
| "Please specify a program using absolute path or make sure " |
| "the program is available in your PATH system variable\n"; |
| ignored = write(STDERR_FILENO, message, strlen(message)); |
| (void)ignored; |
| |
| // Must use _exit() rather than exit() to avoid flushing output buffers |
| // that will also be flushed by the parent. |
| _exit(1); |
| } else { |
| free(argv[0]); |
| |
| close(stdin_pipe[0]); |
| close(stdout_pipe[1]); |
| |
| child_stdin_ = stdin_pipe[1]; |
| child_stdout_ = stdout_pipe[0]; |
| } |
| } |
| |
| bool Subprocess::Communicate(const std::string& input_data, |
| std::string* output_data, std::string* error) { |
| if (child_stdin_ == -1) { |
| std::cerr << "Must call Start() first." << '\n'; |
| UPB_ASSERT(child_stdin_ != -1); |
| } |
| |
| // The "sighandler_t" typedef is GNU-specific, so define our own. |
| typedef void SignalHandler(int); |
| |
| // Make sure SIGPIPE is disabled so that if the child dies it doesn't kill us. |
| SignalHandler* old_pipe_handler = signal(SIGPIPE, SIG_IGN); |
| |
| int input_pos = 0; |
| int max_fd = std::max(child_stdin_, child_stdout_); |
| |
| while (child_stdout_ != -1) { |
| fd_set read_fds; |
| fd_set write_fds; |
| FD_ZERO(&read_fds); |
| FD_ZERO(&write_fds); |
| if (child_stdout_ != -1) { |
| FD_SET(child_stdout_, &read_fds); |
| } |
| if (child_stdin_ != -1) { |
| FD_SET(child_stdin_, &write_fds); |
| } |
| |
| if (select(max_fd + 1, &read_fds, &write_fds, nullptr, nullptr) < 0) { |
| if (errno == EINTR) { |
| // Interrupted by signal. Try again. |
| continue; |
| } else { |
| std::cerr << "select: " << strerror(errno) << '\n'; |
| UPB_ASSERT(0); |
| } |
| } |
| |
| if (child_stdin_ != -1 && FD_ISSET(child_stdin_, &write_fds)) { |
| int n = write(child_stdin_, input_data.data() + input_pos, |
| input_data.size() - input_pos); |
| if (n < 0) { |
| // Child closed pipe. Presumably it will report an error later. |
| // Pretend we're done for now. |
| input_pos = input_data.size(); |
| } else { |
| input_pos += n; |
| } |
| |
| if (input_pos == (int)input_data.size()) { |
| // We're done writing. Close. |
| close(child_stdin_); |
| child_stdin_ = -1; |
| } |
| } |
| |
| if (child_stdout_ != -1 && FD_ISSET(child_stdout_, &read_fds)) { |
| char buffer[4096]; |
| int n = read(child_stdout_, buffer, sizeof(buffer)); |
| |
| if (n > 0) { |
| output_data->append(buffer, (size_t)n); |
| } else { |
| // We're done reading. Close. |
| close(child_stdout_); |
| child_stdout_ = -1; |
| } |
| } |
| } |
| |
| if (child_stdin_ != -1) { |
| // Child did not finish reading input before it closed the output. |
| // Presumably it exited with an error. |
| close(child_stdin_); |
| child_stdin_ = -1; |
| } |
| |
| int status; |
| while (waitpid(child_pid_, &status, 0) == -1) { |
| if (errno != EINTR) { |
| std::cerr << "waitpid: " << strerror(errno) << '\n'; |
| UPB_ASSERT(0); |
| } |
| } |
| |
| // Restore SIGPIPE handling. |
| signal(SIGPIPE, old_pipe_handler); |
| |
| if (WIFEXITED(status)) { |
| if (WEXITSTATUS(status) != 0) { |
| int error_code = WEXITSTATUS(status); |
| *error = |
| absl::Substitute("Plugin failed with status code $0.", error_code); |
| return false; |
| } |
| } else if (WIFSIGNALED(status)) { |
| int signal = WTERMSIG(status); |
| *error = absl::Substitute("Plugin killed by signal $0.", signal); |
| return false; |
| } else { |
| *error = "Neither WEXITSTATUS nor WTERMSIG is true?"; |
| return false; |
| } |
| |
| return true; |
| } |
| |
| #endif // !_WIN32 |
| |
| } // namespace generator |
| } // namespace upb |