| // 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(); |
| } |