| /* |
| * 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, 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::KillAndWaitForTermination(int exit_code) { |
| auto code = exit_code ? static_cast<DWORD>(exit_code) : STATUS_CONTROL_C_EXIT; |
| ::TerminateProcess(*s_->win_proc_handle, 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 |