blob: 859873ac3d946a3a465c139ef8d5be9c742f15f5 [file] [log] [blame]
// Copyright 2012 Google Inc. All Rights Reserved.
//
// 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 <assert.h>
#include <stdio.h>
#include <algorithm>
#include "process_utils.h"
#include "subprocess.h"
#include "util.h"
using namespace std;
Subprocess::Subprocess(SubprocessSet& subprocess_set, bool use_console)
: subprocess_set_(subprocess_set), use_console_(use_console) {}
Subprocess::~Subprocess() {
Stop();
// Reap child if forgotten.
if (child_)
Finish();
}
void Subprocess::Stop() {
pipe_.Close();
}
HANDLE Subprocess::SetupPipe() {
char pipe_name[100];
snprintf(pipe_name, sizeof(pipe_name),
"\\\\.\\pipe\\ninja_pid%lu_sp%p", GetCurrentProcessId(), this);
IpcHandle pipe_handle = ::CreateNamedPipeA(
pipe_name, PIPE_ACCESS_INBOUND | FILE_FLAG_OVERLAPPED, PIPE_TYPE_BYTE,
PIPE_UNLIMITED_INSTANCES, 0, 0, INFINITE, NULL);
if (!pipe_handle)
Win32Fatal("CreateNamedPipe");
AsyncLoop& async_loop = subprocess_set_.async_loop_;
pipe_ = AsyncHandle::Create(
std::move(pipe_handle), subprocess_set_.async_loop_,
[this](AsyncError error, size_t size) {
if (error) {
if (error != ERROR_BROKEN_PIPE)
Win32Fatal("GetOverlappedResult", error);
Stop();
subprocess_set_.OnProcessCompletion(this);
return;
}
if (!is_reading_) {
IpcHandle pipe_handle = pipe_.TakeAcceptedHandle();
pipe_.ResetHandle(std::move(pipe_handle));
is_reading_ = true;
} else {
buf_.append(read_buf_, size);
}
pipe_.StartRead(read_buf_, sizeof(read_buf_));
});
pipe_.StartAccept();
// Get the write end of the pipe as a handle inheritable across processes.
HANDLE output_write_handle =
CreateFileA(pipe_name, GENERIC_WRITE, 0, NULL, OPEN_EXISTING, 0, NULL);
HANDLE output_write_child;
if (!DuplicateHandle(GetCurrentProcess(), output_write_handle,
GetCurrentProcess(), &output_write_child,
0, TRUE, DUPLICATE_SAME_ACCESS)) {
Win32Fatal("DuplicateHandle");
}
CloseHandle(output_write_handle);
return output_write_child;
}
bool Subprocess::Start(const string& command) {
IpcHandle child_pipe = SetupPipe();
SECURITY_ATTRIBUTES security_attributes = {};
security_attributes.nLength = sizeof(SECURITY_ATTRIBUTES);
security_attributes.bInheritHandle = TRUE;
// Must be inheritable so subprocesses can dup to children.
IpcHandle nul =
CreateFileA("NUL", GENERIC_READ,
FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
&security_attributes, OPEN_EXISTING, 0, NULL);
if (!nul)
Fatal("couldn't open nul");
STARTUPINFOA startup_info = {};
startup_info.cb = sizeof(STARTUPINFO);
if (!use_console_) {
startup_info.dwFlags = STARTF_USESTDHANDLES;
startup_info.hStdInput = nul.native_handle();
startup_info.hStdOutput = child_pipe.native_handle();
startup_info.hStdError = child_pipe.native_handle();
}
// In the console case, child_pipe is still inherited by the child and closed
// when the subprocess finishes, which then notifies ninja.
PROCESS_INFORMATION process_info = {};
// Ninja handles ctrl-c, except for subprocesses in console pools.
DWORD process_flags = use_console_ ? 0 : CREATE_NEW_PROCESS_GROUP;
// Compute environment pointer.
LPVOID environment = NULL;
if (subprocess_set_.environment_)
environment = subprocess_set_.environment_->AsAnsiEnvironmentBlock();
// Do not prepend 'cmd /c' on Windows, this breaks command
// lines greater than 8,191 chars.
if (!CreateProcessA(NULL, (char*)command.c_str(), NULL, NULL,
/* inherit handles */ TRUE, process_flags, environment,
NULL, &startup_info, &process_info)) {
DWORD error = GetLastError();
if (error == ERROR_FILE_NOT_FOUND) {
// File (program) not found error is treated as a normal build
// action failure.
Stop();
// child_ is already NULL;
buf_ = "CreateProcess failed: The system cannot find the file "
"specified.\n";
return true;
} else {
fprintf(stderr, "\nCreateProcess failed. Command attempted:\n\"%s\"\n",
command.c_str());
const char* hint = NULL;
// ERROR_INVALID_PARAMETER means the command line was formatted
// incorrectly. This can be caused by a command line being too long or
// leading whitespace in the command. Give extra context for this case.
if (error == ERROR_INVALID_PARAMETER) {
if (command.length() > 0 && (command[0] == ' ' || command[0] == '\t'))
hint = "command contains leading whitespace";
else
hint = "is the command line too long?";
}
Win32Fatal("CreateProcess", hint);
}
}
// Close pipe channel only used by the child.
CloseHandle(process_info.hThread);
child_ = IpcHandle(process_info.hProcess);
return true;
}
ExitStatus Subprocess::Finish() {
if (!child_)
return ExitFailure;
// TODO: add error handling for all of these.
WaitForSingleObject(child_.native_handle(), INFINITE);
DWORD exit_code = 0;
GetExitCodeProcess(child_.native_handle(), &exit_code);
child_.Close();
return exit_code == 0 ? ExitSuccess :
exit_code == CONTROL_C_EXIT ? ExitInterrupted :
ExitFailure;
}
bool Subprocess::Done() const {
return !pipe_;
}
const string& Subprocess::GetOutput() const {
return buf_;
}
SubprocessSet::SubprocessSet()
: async_loop_(AsyncLoop::Get()), interrupt_catcher_(async_loop_) {}
SubprocessSet::SubprocessSet(const EnvironmentBlock& environment)
: async_loop_(AsyncLoop::Get()), interrupt_catcher_(async_loop_),
environment_(&environment) {}
SubprocessSet::~SubprocessSet() {
Clear();
}
Subprocess *SubprocessSet::Add(const string& command, bool use_console) {
std::unique_ptr<Subprocess> subprocess(new Subprocess(*this, use_console));
if (!subprocess->Start(command)) {
return 0;
}
Subprocess* result = subprocess.get();
if (subprocess->child_)
running_.push_back(std::move(subprocess));
else
finished_.push(std::move(subprocess));
return result;
}
void SubprocessSet::OnProcessCompletion(Subprocess* subprocess) {
// Move the subprocess from the running_ vector to the finished_ queue.
for (auto it = running_.begin(); it != running_.end(); ++it) {
if (it->get() == subprocess) {
finished_.emplace(std::move(*it));
running_.erase(it);
return;
}
}
assert(false && "Unknown subprocess pointer!");
}
bool SubprocessSet::DoWork() {
size_t running_count = running_.size();
if (!running_count)
return false;
AsyncLoop::ExitStatus loop_status =
async_loop_.RunUntil([this, &running_count]() -> bool {
return running_.size() != running_count;
});
return loop_status == AsyncLoop::ExitInterrupted;
}
std::unique_ptr<Subprocess> SubprocessSet::NextFinished() {
if (finished_.empty())
return {};
std::unique_ptr<Subprocess> subproc = std::move(finished_.front());
finished_.pop();
return subproc;
}
void SubprocessSet::Clear() {
for (auto& subproc : running_) {
// Since the foreground process is in our process group, it will receive a
// CTRL_C_EVENT or CTRL_BREAK_EVENT at the same time as us.
if (subproc->child_ && !subproc->use_console_) {
if (!GenerateConsoleCtrlEvent(
CTRL_BREAK_EVENT,
GetProcessId(subproc->child_.native_handle()))) {
Win32Fatal("GenerateConsoleCtrlEvent");
}
}
}
running_.clear();
}