| // Copyright 2014 The Chromium Authors. All rights reserved. | |
| // Use of this source code is governed by a BSD-style license that can be | |
| // found in the LICENSE file. | |
| #include "tools/gn/exec_process.h" | |
| #include <stddef.h> | |
| #include <memory> | |
| #include "base/command_line.h" | |
| #include "base/files/file_util.h" | |
| #include "base/logging.h" | |
| #include "util/build_config.h" | |
| #if defined(OS_WIN) | |
| #include <windows.h> | |
| #include "base/win/scoped_handle.h" | |
| #include "base/win/scoped_process_information.h" | |
| #include "base/win/win_util.h" | |
| #else | |
| #include <errno.h> | |
| #include <fcntl.h> | |
| #include <sys/signal.h> | |
| #include <sys/wait.h> | |
| #include <unistd.h> | |
| #include "base/posix/eintr_wrapper.h" | |
| #include "base/posix/file_descriptor_shuffle.h" | |
| #endif | |
| namespace internal { | |
| #if defined(OS_WIN) | |
| bool ExecProcess(const base::CommandLine& cmdline, | |
| const base::FilePath& startup_dir, | |
| std::string* std_out, | |
| std::string* std_err, | |
| int* exit_code) { | |
| return ExecProcess(cmdline.GetCommandLineString(), startup_dir, std_out, | |
| std_err, exit_code); | |
| } | |
| bool ExecProcess(const base::string16& cmdline_str, | |
| const base::FilePath& startup_dir, | |
| std::string* std_out, | |
| std::string* std_err, | |
| int* exit_code) { | |
| SECURITY_ATTRIBUTES sa_attr; | |
| // Set the bInheritHandle flag so pipe handles are inherited. | |
| sa_attr.nLength = sizeof(SECURITY_ATTRIBUTES); | |
| sa_attr.bInheritHandle = TRUE; | |
| sa_attr.lpSecurityDescriptor = nullptr; | |
| // Create the pipe for the child process's STDOUT. | |
| HANDLE out_read = nullptr; | |
| HANDLE out_write = nullptr; | |
| if (!CreatePipe(&out_read, &out_write, &sa_attr, 0)) { | |
| NOTREACHED() << "Failed to create pipe"; | |
| return false; | |
| } | |
| base::win::ScopedHandle scoped_out_read(out_read); | |
| base::win::ScopedHandle scoped_out_write(out_write); | |
| // Create the pipe for the child process's STDERR. | |
| HANDLE err_read = nullptr; | |
| HANDLE err_write = nullptr; | |
| if (!CreatePipe(&err_read, &err_write, &sa_attr, 0)) { | |
| NOTREACHED() << "Failed to create pipe"; | |
| return false; | |
| } | |
| base::win::ScopedHandle scoped_err_read(err_read); | |
| base::win::ScopedHandle scoped_err_write(err_write); | |
| // Ensure the read handle to the pipe for STDOUT/STDERR is not inherited. | |
| if (!SetHandleInformation(out_read, HANDLE_FLAG_INHERIT, 0)) { | |
| NOTREACHED() << "Failed to disable pipe inheritance"; | |
| return false; | |
| } | |
| if (!SetHandleInformation(err_read, HANDLE_FLAG_INHERIT, 0)) { | |
| NOTREACHED() << "Failed to disable pipe inheritance"; | |
| return false; | |
| } | |
| STARTUPINFO start_info = {}; | |
| start_info.cb = sizeof(STARTUPINFO); | |
| start_info.hStdOutput = out_write; | |
| // Keep the normal stdin. | |
| start_info.hStdInput = GetStdHandle(STD_INPUT_HANDLE); | |
| // FIXME(brettw) set stderr here when we actually read it below. | |
| // start_info.hStdError = err_write; | |
| start_info.hStdError = GetStdHandle(STD_ERROR_HANDLE); | |
| start_info.dwFlags |= STARTF_USESTDHANDLES; | |
| base::string16 cmdline_writable = cmdline_str; | |
| // Create the child process. | |
| PROCESS_INFORMATION temp_process_info = {}; | |
| if (!CreateProcess( | |
| nullptr, base::ToWCharT(&cmdline_writable[0]), nullptr, nullptr, | |
| TRUE, // Handles are inherited. | |
| NORMAL_PRIORITY_CLASS, nullptr, base::ToWCharT(&startup_dir.value()), | |
| &start_info, &temp_process_info)) { | |
| return false; | |
| } | |
| base::win::ScopedProcessInformation proc_info(temp_process_info); | |
| // Close our writing end of pipes now. Otherwise later read would not be | |
| // able to detect end of child's output. | |
| scoped_out_write.Close(); | |
| scoped_err_write.Close(); | |
| // Read output from the child process's pipe for STDOUT | |
| const int kBufferSize = 1024; | |
| char buffer[kBufferSize]; | |
| // FIXME(brettw) read from stderr here! This is complicated because we want | |
| // to read both of them at the same time, probably need overlapped I/O. | |
| // Also uncomment start_info code above. | |
| for (;;) { | |
| DWORD bytes_read = 0; | |
| BOOL success = | |
| ReadFile(out_read, buffer, kBufferSize, &bytes_read, nullptr); | |
| if (!success || bytes_read == 0) | |
| break; | |
| std_out->append(buffer, bytes_read); | |
| } | |
| // Let's wait for the process to finish. | |
| WaitForSingleObject(proc_info.process_handle(), INFINITE); | |
| DWORD dw_exit_code; | |
| GetExitCodeProcess(proc_info.process_handle(), &dw_exit_code); | |
| *exit_code = static_cast<int>(dw_exit_code); | |
| return true; | |
| } | |
| #else | |
| // Reads from the provided file descriptor and appends to output. Returns false | |
| // if the fd is closed or there is an unexpected error (not | |
| // EINTR/EAGAIN/EWOULDBLOCK). | |
| bool ReadFromPipe(int fd, std::string* output) { | |
| char buffer[256]; | |
| int bytes_read = HANDLE_EINTR(read(fd, buffer, sizeof(buffer))); | |
| if (bytes_read == -1) { | |
| return errno == EAGAIN || errno == EWOULDBLOCK; | |
| } else if (bytes_read <= 0) { | |
| return false; | |
| } | |
| output->append(buffer, bytes_read); | |
| return true; | |
| } | |
| bool WaitForExit(int pid, int* exit_code) { | |
| int status; | |
| if (waitpid(pid, &status, 0) < 0) { | |
| PLOG(ERROR) << "waitpid"; | |
| return false; | |
| } | |
| if (WIFEXITED(status)) { | |
| *exit_code = WEXITSTATUS(status); | |
| return true; | |
| } else if (WIFSIGNALED(status)) { | |
| if (WTERMSIG(status) == SIGINT || WTERMSIG(status) == SIGTERM || | |
| WTERMSIG(status) == SIGHUP) | |
| return false; | |
| } | |
| return false; | |
| } | |
| bool ExecProcess(const base::CommandLine& cmdline, | |
| const base::FilePath& startup_dir, | |
| std::string* std_out, | |
| std::string* std_err, | |
| int* exit_code) { | |
| *exit_code = EXIT_FAILURE; | |
| std::vector<std::string> argv = cmdline.argv(); | |
| int out_fd[2], err_fd[2]; | |
| pid_t pid; | |
| base::InjectiveMultimap fd_shuffle1, fd_shuffle2; | |
| std::unique_ptr<char*[]> argv_cstr(new char*[argv.size() + 1]); | |
| fd_shuffle1.reserve(3); | |
| fd_shuffle2.reserve(3); | |
| if (pipe(out_fd) < 0) | |
| return false; | |
| base::ScopedFD out_read(out_fd[0]), out_write(out_fd[1]); | |
| if (pipe(err_fd) < 0) | |
| return false; | |
| base::ScopedFD err_read(err_fd[0]), err_write(err_fd[1]); | |
| if (out_read.get() >= FD_SETSIZE || err_read.get() >= FD_SETSIZE) | |
| return false; | |
| switch (pid = fork()) { | |
| case -1: // error | |
| return false; | |
| case 0: // child | |
| { | |
| // DANGER: no calls to malloc are allowed from now on: | |
| // http://crbug.com/36678 | |
| // | |
| // STL iterators are also not allowed (including those implied | |
| // by range-based for loops), since debug iterators use locks. | |
| // Obscure fork() rule: in the child, if you don't end up doing exec*(), | |
| // you call _exit() instead of exit(). This is because _exit() does not | |
| // call any previously-registered (in the parent) exit handlers, which | |
| // might do things like block waiting for threads that don't even exist | |
| // in the child. | |
| int dev_null = open("/dev/null", O_WRONLY); | |
| if (dev_null < 0) | |
| _exit(127); | |
| fd_shuffle1.push_back( | |
| base::InjectionArc(out_write.get(), STDOUT_FILENO, true)); | |
| fd_shuffle1.push_back( | |
| base::InjectionArc(err_write.get(), STDERR_FILENO, true)); | |
| fd_shuffle1.push_back(base::InjectionArc(dev_null, STDIN_FILENO, true)); | |
| // Adding another element here? Remeber to increase the argument to | |
| // reserve(), above. | |
| // DANGER: Do NOT convert to range-based for loop! | |
| for (size_t i = 0; i < fd_shuffle1.size(); ++i) | |
| fd_shuffle2.push_back(fd_shuffle1[i]); | |
| if (!ShuffleFileDescriptors(&fd_shuffle1)) | |
| _exit(127); | |
| base::SetCurrentDirectory(startup_dir); | |
| // TODO(brettw) the base version GetAppOutput does a | |
| // CloseSuperfluousFds call here. Do we need this? | |
| // DANGER: Do NOT convert to range-based for loop! | |
| for (size_t i = 0; i < argv.size(); i++) | |
| argv_cstr[i] = const_cast<char*>(argv[i].c_str()); | |
| argv_cstr[argv.size()] = nullptr; | |
| execvp(argv_cstr[0], argv_cstr.get()); | |
| _exit(127); | |
| } | |
| default: // parent | |
| { | |
| // Close our writing end of pipe now. Otherwise later read would not | |
| // be able to detect end of child's output (in theory we could still | |
| // write to the pipe). | |
| out_write.reset(); | |
| err_write.reset(); | |
| bool out_open = true, err_open = true; | |
| while (out_open || err_open) { | |
| fd_set read_fds; | |
| FD_ZERO(&read_fds); | |
| FD_SET(out_read.get(), &read_fds); | |
| FD_SET(err_read.get(), &read_fds); | |
| int res = | |
| HANDLE_EINTR(select(std::max(out_read.get(), err_read.get()) + 1, | |
| &read_fds, nullptr, nullptr, nullptr)); | |
| if (res <= 0) | |
| break; | |
| if (FD_ISSET(out_read.get(), &read_fds)) | |
| out_open = ReadFromPipe(out_read.get(), std_out); | |
| if (FD_ISSET(err_read.get(), &read_fds)) | |
| err_open = ReadFromPipe(err_read.get(), std_err); | |
| } | |
| return WaitForExit(pid, exit_code); | |
| } | |
| } | |
| return false; | |
| } | |
| #endif | |
| } // namespace internal |