/* * 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 #include #include #include #include #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(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); 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 == kBuffer || args.stdout_mode == kBuffer) { s_->stdouterr_pipe = Pipe::Create(); PERFETTO_CHECK( ::SetHandleInformation(*s_->stdouterr_pipe.wr, HANDLE_FLAG_INHERIT, 1)); } ScopedPlatformHandle nul_handle; if (args.stderr_mode == kDevNull || args.stdout_mode == kDevNull) { nul_handle.reset(::CreateFileA("NUL", GENERIC_WRITE, 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 == kInherit) { start_info.hStdError = ::GetStdHandle(STD_ERROR_HANDLE); } else if (args.stderr_mode == kBuffer) { start_info.hStdError = *s_->stdouterr_pipe.wr; } else if (args.stderr_mode == kDevNull) { start_info.hStdError = *nul_handle; } else if (args.stderr_mode == 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 == kInherit) { start_info.hStdOutput = ::GetStdHandle(STD_OUTPUT_HANDLE); } else if (args.stdout_mode == kBuffer) { start_info.hStdOutput = *s_->stdouterr_pipe.wr; } else if (args.stdout_mode == kDevNull) { start_info.hStdOutput = *nul_handle; } else if (args.stdout_mode == kFd) { PERFETTO_CHECK( ::SetHandleInformation(*args.out_fd, HANDLE_FLAG_INHERIT, 1)); start_info.hStdOutput = *args.out_fd; } else { PERFETTO_CHECK(false); } start_info.hStdInput = *s_->stdin_pipe.rd; 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(); s_->stdin_thread = std::thread(&Subprocess::StdinThread, s, args.input); if (args.stderr_mode == kBuffer || args.stdout_mode == 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(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 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 lock(s->mutex); s->locked_outerr_buf.append(buf, static_cast(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 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 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 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(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 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(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(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