| /* | |
| * Copyright (C) 2020 The Android Open Source Project | |
| * | |
| * Licensed under the Apache License, Version 2.0 (the "License"); | |
| * you may not use this file except in compliance with the License. | |
| * You may obtain a copy of the License at | |
| * | |
| * http://www.apache.org/licenses/LICENSE-2.0 | |
| * | |
| * Unless required by applicable law or agreed to in writing, software | |
| * distributed under the License is distributed on an "AS IS" BASIS, | |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
| * See the License for the specific language governing permissions and | |
| * limitations under the License. | |
| */ | |
| #include "perfetto/ext/base/subprocess.h" | |
| #include "perfetto/base/build_config.h" | |
| #if PERFETTO_BUILDFLAG(PERFETTO_OS_WIN) | |
| #include <stdio.h> | |
| #include <algorithm> | |
| #include <mutex> | |
| #include <tuple> | |
| #include <windows.h> | |
| #include "perfetto/base/logging.h" | |
| #include "perfetto/base/time.h" | |
| #include "perfetto/ext/base/pipe.h" | |
| #include "perfetto/ext/base/utils.h" | |
| namespace perfetto { | |
| namespace base { | |
| // static | |
| const int Subprocess::kTimeoutSignal = static_cast<int>(STATUS_TIMEOUT); | |
| void Subprocess::Start() { | |
| if (args.exec_cmd.empty()) { | |
| PERFETTO_ELOG("Subprocess.exec_cmd cannot be empty on Windows"); | |
| return; | |
| } | |
| // Quote arguments but only when ambiguous. When quoting, CreateProcess() | |
| // assumes that the command is an absolute path and does not search in the | |
| // %PATH%. If non quoted, instead, CreateProcess() tries both. This is to | |
| // allow Subprocess("cmd.exe", "/c", "shell command"). | |
| std::string cmd; | |
| for (const auto& part : args.exec_cmd) { | |
| if (part.find(" ") != std::string::npos) { | |
| cmd += "\"" + part + "\" "; | |
| } else { | |
| cmd += part + " "; | |
| } | |
| } | |
| // Remove trailing space. | |
| if (!cmd.empty()) | |
| cmd.resize(cmd.size() - 1); | |
| if (args.stdin_mode == InputMode::kBuffer) { | |
| s_->stdin_pipe = Pipe::Create(); | |
| // Allow the child process to inherit the other end of the pipe. | |
| PERFETTO_CHECK( | |
| ::SetHandleInformation(*s_->stdin_pipe.rd, HANDLE_FLAG_INHERIT, 1)); | |
| } | |
| if (args.stderr_mode == OutputMode::kBuffer || | |
| args.stdout_mode == OutputMode::kBuffer) { | |
| s_->stdouterr_pipe = Pipe::Create(); | |
| PERFETTO_CHECK( | |
| ::SetHandleInformation(*s_->stdouterr_pipe.wr, HANDLE_FLAG_INHERIT, 1)); | |
| } | |
| ScopedPlatformHandle nul_handle; | |
| if (args.stderr_mode == OutputMode::kDevNull || | |
| args.stdout_mode == OutputMode::kDevNull) { | |
| nul_handle.reset(::CreateFileA( | |
| "NUL", GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, | |
| nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr)); | |
| PERFETTO_CHECK(::SetHandleInformation(*nul_handle, HANDLE_FLAG_INHERIT, 1)); | |
| } | |
| PROCESS_INFORMATION proc_info{}; | |
| STARTUPINFOA start_info{}; | |
| start_info.cb = sizeof(STARTUPINFOA); | |
| if (args.stderr_mode == OutputMode::kInherit) { | |
| start_info.hStdError = ::GetStdHandle(STD_ERROR_HANDLE); | |
| } else if (args.stderr_mode == OutputMode::kBuffer) { | |
| start_info.hStdError = *s_->stdouterr_pipe.wr; | |
| } else if (args.stderr_mode == OutputMode::kDevNull) { | |
| start_info.hStdError = *nul_handle; | |
| } else if (args.stderr_mode == OutputMode::kFd) { | |
| PERFETTO_CHECK( | |
| ::SetHandleInformation(*args.out_fd, HANDLE_FLAG_INHERIT, 1)); | |
| start_info.hStdError = *args.out_fd; | |
| } else { | |
| PERFETTO_CHECK(false); | |
| } | |
| if (args.stdout_mode == OutputMode::kInherit) { | |
| start_info.hStdOutput = ::GetStdHandle(STD_OUTPUT_HANDLE); | |
| } else if (args.stdout_mode == OutputMode::kBuffer) { | |
| start_info.hStdOutput = *s_->stdouterr_pipe.wr; | |
| } else if (args.stdout_mode == OutputMode::kDevNull) { | |
| start_info.hStdOutput = *nul_handle; | |
| } else if (args.stdout_mode == OutputMode::kFd) { | |
| PERFETTO_CHECK( | |
| ::SetHandleInformation(*args.out_fd, HANDLE_FLAG_INHERIT, 1)); | |
| start_info.hStdOutput = *args.out_fd; | |
| } else { | |
| PERFETTO_CHECK(false); | |
| } | |
| if (args.stdin_mode == InputMode::kBuffer) { | |
| start_info.hStdInput = *s_->stdin_pipe.rd; | |
| } else if (args.stdin_mode == InputMode::kDevNull) { | |
| start_info.hStdInput = *nul_handle; | |
| } | |
| start_info.dwFlags |= STARTF_USESTDHANDLES; | |
| // Create the child process. | |
| bool success = | |
| ::CreateProcessA(nullptr, // App name. Needs to be null to use PATH. | |
| &cmd[0], // Command line. | |
| nullptr, // Process security attributes. | |
| nullptr, // Primary thread security attributes. | |
| true, // Handles are inherited. | |
| 0, // Flags. | |
| nullptr, // Use parent's environment. | |
| nullptr, // Use parent's current directory. | |
| &start_info, // STARTUPINFO pointer. | |
| &proc_info); // Receives PROCESS_INFORMATION. | |
| // Close on our side the pipe ends that we passed to the child process. | |
| s_->stdin_pipe.rd.reset(); | |
| s_->stdouterr_pipe.wr.reset(); | |
| args.out_fd.reset(); | |
| if (!success) { | |
| s_->returncode = ERROR_FILE_NOT_FOUND; | |
| s_->status = kTerminated; | |
| s_->stdin_pipe.wr.reset(); | |
| s_->stdouterr_pipe.rd.reset(); | |
| PERFETTO_ELOG("CreateProcess failed: %lx, cmd: %s", GetLastError(), | |
| &cmd[0]); | |
| return; | |
| } | |
| s_->pid = proc_info.dwProcessId; | |
| s_->win_proc_handle = ScopedPlatformHandle(proc_info.hProcess); | |
| s_->win_thread_handle = ScopedPlatformHandle(proc_info.hThread); | |
| s_->status = kRunning; | |
| MovableState* s = s_.get(); | |
| if (args.stdin_mode == InputMode::kBuffer) { | |
| s_->stdin_thread = std::thread(&Subprocess::StdinThread, s, args.input); | |
| } | |
| if (args.stderr_mode == OutputMode::kBuffer || | |
| args.stdout_mode == OutputMode::kBuffer) { | |
| PERFETTO_DCHECK(s_->stdouterr_pipe.rd); | |
| s_->stdouterr_thread = std::thread(&Subprocess::StdoutErrThread, s); | |
| } | |
| } | |
| // static | |
| void Subprocess::StdinThread(MovableState* s, const std::string& input) { | |
| size_t input_written = 0; | |
| while (input_written < input.size()) { | |
| DWORD wsize = 0; | |
| if (::WriteFile(*s->stdin_pipe.wr, input.data() + input_written, | |
| static_cast<DWORD>(input.size() - input_written), &wsize, | |
| nullptr)) { | |
| input_written += wsize; | |
| } else { | |
| // ERROR_BROKEN_PIPE is WAI when the child just closes stdin and stops | |
| // accepting input. | |
| auto err = ::GetLastError(); | |
| if (err != ERROR_BROKEN_PIPE) | |
| PERFETTO_PLOG("Subprocess WriteFile(stdin) failed %lx", err); | |
| break; | |
| } | |
| } // while(...) | |
| std::unique_lock<std::mutex> lock(s->mutex); | |
| s->stdin_pipe.wr.reset(); | |
| } | |
| // static | |
| void Subprocess::StdoutErrThread(MovableState* s) { | |
| char buf[4096]; | |
| for (;;) { | |
| DWORD rsize = 0; | |
| bool res = | |
| ::ReadFile(*s->stdouterr_pipe.rd, buf, sizeof(buf), &rsize, nullptr); | |
| if (!res) { | |
| auto err = GetLastError(); | |
| if (err != ERROR_BROKEN_PIPE) | |
| PERFETTO_PLOG("Subprocess ReadFile(stdouterr) failed %ld", err); | |
| } | |
| if (rsize > 0) { | |
| std::unique_lock<std::mutex> lock(s->mutex); | |
| s->locked_outerr_buf.append(buf, static_cast<size_t>(rsize)); | |
| } else { // EOF or some error. | |
| break; | |
| } | |
| } // For(..) | |
| // Close the stdouterr_pipe. The main loop looks at the pipe closure to | |
| // determine whether the stdout/err thread has completed. | |
| { | |
| std::unique_lock<std::mutex> lock(s->mutex); | |
| s->stdouterr_pipe.rd.reset(); | |
| } | |
| s->stdouterr_done_event.Notify(); | |
| } | |
| Subprocess::Status Subprocess::Poll() { | |
| if (s_->status != kRunning) | |
| return s_->status; // Nothing to poll. | |
| Wait(1 /*ms*/); | |
| return s_->status; | |
| } | |
| bool Subprocess::Wait(int timeout_ms) { | |
| PERFETTO_CHECK(s_->status != kNotStarted); | |
| const bool wait_forever = timeout_ms == 0; | |
| const int64_t wait_start_ms = base::GetWallTimeMs().count(); | |
| // Break out of the loop only after both conditions are satisfied: | |
| // - All stdout/stderr data has been read (if OutputMode::kBuffer). | |
| // - The process exited. | |
| // Note that the two events can happen arbitrary order. After the process | |
| // exits, there might be still data in the pipe buffer, which we want to | |
| // read fully. | |
| // Note also that stdout/err might be "complete" before starting, if neither | |
| // is operating in OutputMode::kBuffer mode. In that case we just want to wait | |
| // for the process termination. | |
| // | |
| // Instead, don't wait on the stdin to be fully written. The child process | |
| // might exit prematurely (or crash). If that happens, we can end up in a | |
| // state where the write(stdin_pipe_.wr) will never unblock. | |
| bool stdouterr_complete = false; | |
| for (;;) { | |
| HANDLE wait_handles[2]{}; | |
| DWORD num_handles = 0; | |
| // Check if the process exited. | |
| bool process_exited = !s_->win_proc_handle; | |
| if (!process_exited) { | |
| DWORD exit_code = STILL_ACTIVE; | |
| PERFETTO_CHECK(::GetExitCodeProcess(*s_->win_proc_handle, &exit_code)); | |
| if (exit_code != STILL_ACTIVE) { | |
| s_->returncode = static_cast<int>(exit_code); | |
| s_->status = kTerminated; | |
| s_->win_proc_handle.reset(); | |
| s_->win_thread_handle.reset(); | |
| process_exited = true; | |
| } | |
| } else { | |
| PERFETTO_DCHECK(s_->status != kRunning); | |
| } | |
| if (!process_exited) { | |
| wait_handles[num_handles++] = *s_->win_proc_handle; | |
| } | |
| // Check if there is more output and if the stdout/err pipe has been closed. | |
| { | |
| std::unique_lock<std::mutex> lock(s_->mutex); | |
| // Move the output from the internal buffer shared with the | |
| // stdouterr_thread to the final buffer exposed to the client. | |
| if (!s_->locked_outerr_buf.empty()) { | |
| s_->output.append(std::move(s_->locked_outerr_buf)); | |
| s_->locked_outerr_buf.clear(); | |
| } | |
| stdouterr_complete = !s_->stdouterr_pipe.rd; | |
| if (!stdouterr_complete) { | |
| wait_handles[num_handles++] = s_->stdouterr_done_event.fd(); | |
| } | |
| } // lock(s_->mutex) | |
| if (num_handles == 0) { | |
| PERFETTO_DCHECK(process_exited && stdouterr_complete); | |
| break; | |
| } | |
| DWORD wait_ms; // Note: DWORD is unsigned. | |
| if (wait_forever) { | |
| wait_ms = INFINITE; | |
| } else { | |
| const int64_t now = GetWallTimeMs().count(); | |
| const int64_t wait_left_ms = timeout_ms - (now - wait_start_ms); | |
| if (wait_left_ms <= 0) | |
| return false; // Timed out | |
| wait_ms = static_cast<DWORD>(wait_left_ms); | |
| } | |
| auto wait_res = | |
| ::WaitForMultipleObjects(num_handles, wait_handles, false, wait_ms); | |
| PERFETTO_CHECK(wait_res != WAIT_FAILED); | |
| } | |
| PERFETTO_DCHECK(!s_->win_proc_handle); | |
| PERFETTO_DCHECK(!s_->win_thread_handle); | |
| if (s_->stdin_thread.joinable()) // Might not exist if CreateProcess failed. | |
| s_->stdin_thread.join(); | |
| if (s_->stdouterr_thread.joinable()) | |
| s_->stdouterr_thread.join(); | |
| // The stdin pipe is closed by the dedicated stdin thread. However if that is | |
| // not started (e.g. because of no redirection) force close it now. Needs to | |
| // happen after the join() to be thread safe. | |
| s_->stdin_pipe.wr.reset(); | |
| s_->stdouterr_pipe.rd.reset(); | |
| return true; | |
| } | |
| void Subprocess::Kill(int exit_code) { | |
| auto code = exit_code ? static_cast<DWORD>(exit_code) : STATUS_CONTROL_C_EXIT; | |
| ::TerminateProcess(*s_->win_proc_handle, code); | |
| } | |
| void Subprocess::KillAndWaitForTermination(int exit_code) { | |
| Kill(exit_code); | |
| Wait(); | |
| // TryReadExitStatus must have joined the threads. | |
| PERFETTO_DCHECK(!s_->stdin_thread.joinable()); | |
| PERFETTO_DCHECK(!s_->stdouterr_thread.joinable()); | |
| } | |
| } // namespace base | |
| } // namespace perfetto | |
| #endif // PERFETTO_OS_WIN |