#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 {
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;
}
std::string cmd;
for (const auto& part : args.exec_cmd) {
if (part.find(" ") != std::string::npos) {
cmd += "\"" + part + "\" ";
} else {
cmd += part + " ";
}
}
if (!cmd.empty())
cmd.resize(cmd.size() - 1);
if (args.stdin_mode == InputMode::kBuffer) {
s_->stdin_pipe = Pipe::Create();
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;
bool success =
::CreateProcessA(nullptr,
&cmd[0],
nullptr,
nullptr,
true,
0,
nullptr,
nullptr,
&start_info,
&proc_info);
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);
}
}
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 {
auto err = ::GetLastError();
if (err != ERROR_BROKEN_PIPE)
PERFETTO_PLOG("Subprocess WriteFile(stdin) failed %lx", err);
break;
}
}
std::unique_lock<std::mutex> lock(s->mutex);
s->stdin_pipe.wr.reset();
}
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 {
break;
}
}
{
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;
Wait(1 );
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();
bool stdouterr_complete = false;
for (;;) {
HANDLE wait_handles[2]{};
DWORD num_handles = 0;
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;
}
{
std::unique_lock<std::mutex> lock(s_->mutex);
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();
}
}
if (num_handles == 0) {
PERFETTO_DCHECK(process_exited && stdouterr_complete);
break;
}
DWORD wait_ms;
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;
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())
s_->stdin_thread.join();
if (s_->stdouterr_thread.joinable())
s_->stdouterr_thread.join();
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();
PERFETTO_DCHECK(!s_->stdin_thread.joinable());
PERFETTO_DCHECK(!s_->stdouterr_thread.joinable());
}
}
}
#endif