| // 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 "subprocess.h" |
| |
| #include <assert.h> |
| #include <stdio.h> |
| |
| #include <algorithm> |
| |
| #include "util.h" |
| |
| using namespace std; |
| |
| Subprocess::Subprocess(bool use_console) : child_(NULL) , overlapped_(), |
| is_reading_(false), |
| use_console_(use_console) { |
| } |
| |
| Subprocess::~Subprocess() { |
| if (pipe_) { |
| if (!CloseHandle(pipe_)) |
| Win32Fatal("CloseHandle"); |
| } |
| // Reap child if forgotten. |
| if (child_) |
| Finish(); |
| } |
| |
| HANDLE Subprocess::SetupPipe(HANDLE ioport) { |
| char pipe_name[100]; |
| snprintf(pipe_name, sizeof(pipe_name), |
| "\\\\.\\pipe\\ninja_pid%lu_sp%p", GetCurrentProcessId(), this); |
| |
| pipe_ = ::CreateNamedPipeA(pipe_name, |
| PIPE_ACCESS_INBOUND | FILE_FLAG_OVERLAPPED, |
| PIPE_TYPE_BYTE, |
| PIPE_UNLIMITED_INSTANCES, |
| 0, 0, INFINITE, NULL); |
| if (pipe_ == INVALID_HANDLE_VALUE) |
| Win32Fatal("CreateNamedPipe"); |
| |
| if (!CreateIoCompletionPort(pipe_, ioport, (ULONG_PTR)this, 0)) |
| Win32Fatal("CreateIoCompletionPort"); |
| |
| memset(&overlapped_, 0, sizeof(overlapped_)); |
| if (!ConnectNamedPipe(pipe_, &overlapped_) && |
| GetLastError() != ERROR_IO_PENDING) { |
| Win32Fatal("ConnectNamedPipe"); |
| } |
| |
| // 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(SubprocessSet* set, const string& command) { |
| HANDLE child_pipe = SetupPipe(set->ioport_); |
| |
| SECURITY_ATTRIBUTES security_attributes; |
| memset(&security_attributes, 0, sizeof(SECURITY_ATTRIBUTES)); |
| security_attributes.nLength = sizeof(SECURITY_ATTRIBUTES); |
| security_attributes.bInheritHandle = TRUE; |
| // Must be inheritable so subprocesses can dup to children. |
| HANDLE nul = |
| CreateFileA("NUL", GENERIC_READ, |
| FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, |
| &security_attributes, OPEN_EXISTING, 0, NULL); |
| if (nul == INVALID_HANDLE_VALUE) |
| Fatal("couldn't open nul"); |
| |
| STARTUPINFOA startup_info; |
| memset(&startup_info, 0, sizeof(startup_info)); |
| startup_info.cb = sizeof(STARTUPINFO); |
| if (!use_console_) { |
| startup_info.dwFlags = STARTF_USESTDHANDLES; |
| startup_info.hStdInput = nul; |
| startup_info.hStdOutput = child_pipe; |
| startup_info.hStdError = child_pipe; |
| } |
| // 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; |
| memset(&process_info, 0, sizeof(process_info)); |
| |
| // Ninja handles ctrl-c, except for subprocesses in console pools. |
| DWORD process_flags = use_console_ ? 0 : CREATE_NEW_PROCESS_GROUP; |
| |
| // 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, |
| NULL, 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. |
| if (child_pipe) |
| CloseHandle(child_pipe); |
| CloseHandle(pipe_); |
| CloseHandle(nul); |
| pipe_ = NULL; |
| // 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. |
| if (child_pipe) |
| CloseHandle(child_pipe); |
| CloseHandle(nul); |
| |
| CloseHandle(process_info.hThread); |
| child_ = process_info.hProcess; |
| |
| return true; |
| } |
| |
| void Subprocess::OnPipeReady() { |
| DWORD bytes; |
| if (!GetOverlappedResult(pipe_, &overlapped_, &bytes, TRUE)) { |
| if (GetLastError() == ERROR_BROKEN_PIPE) { |
| CloseHandle(pipe_); |
| pipe_ = NULL; |
| return; |
| } |
| Win32Fatal("GetOverlappedResult"); |
| } |
| |
| if (is_reading_ && bytes) |
| buf_.append(overlapped_buf_, bytes); |
| |
| memset(&overlapped_, 0, sizeof(overlapped_)); |
| is_reading_ = true; |
| if (!::ReadFile(pipe_, overlapped_buf_, sizeof(overlapped_buf_), |
| &bytes, &overlapped_)) { |
| if (GetLastError() == ERROR_BROKEN_PIPE) { |
| CloseHandle(pipe_); |
| pipe_ = NULL; |
| return; |
| } |
| if (GetLastError() != ERROR_IO_PENDING) |
| Win32Fatal("ReadFile"); |
| } |
| |
| // Even if we read any bytes in the readfile call, we'll enter this |
| // function again later and get them at that point. |
| } |
| |
| ExitStatus Subprocess::Finish() { |
| if (!child_) |
| return ExitFailure; |
| |
| // TODO: add error handling for all of these. |
| WaitForSingleObject(child_, INFINITE); |
| |
| DWORD exit_code = 0; |
| GetExitCodeProcess(child_, &exit_code); |
| |
| CloseHandle(child_); |
| child_ = NULL; |
| |
| return exit_code == 0 ? ExitSuccess : |
| exit_code == CONTROL_C_EXIT ? ExitInterrupted : |
| ExitFailure; |
| } |
| |
| bool Subprocess::Done() const { |
| return pipe_ == NULL; |
| } |
| |
| const string& Subprocess::GetOutput() const { |
| return buf_; |
| } |
| |
| HANDLE SubprocessSet::ioport_; |
| |
| SubprocessSet::SubprocessSet() { |
| ioport_ = ::CreateIoCompletionPort(INVALID_HANDLE_VALUE, NULL, 0, 1); |
| if (!ioport_) |
| Win32Fatal("CreateIoCompletionPort"); |
| if (!SetConsoleCtrlHandler(NotifyInterrupted, TRUE)) |
| Win32Fatal("SetConsoleCtrlHandler"); |
| } |
| |
| SubprocessSet::~SubprocessSet() { |
| Clear(); |
| |
| SetConsoleCtrlHandler(NotifyInterrupted, FALSE); |
| CloseHandle(ioport_); |
| } |
| |
| BOOL WINAPI SubprocessSet::NotifyInterrupted(DWORD dwCtrlType) { |
| if (dwCtrlType == CTRL_C_EVENT || dwCtrlType == CTRL_BREAK_EVENT) { |
| if (!PostQueuedCompletionStatus(ioport_, 0, 0, NULL)) |
| Win32Fatal("PostQueuedCompletionStatus"); |
| return TRUE; |
| } |
| |
| return FALSE; |
| } |
| |
| Subprocess *SubprocessSet::Add(const string& command, bool use_console) { |
| Subprocess *subprocess = new Subprocess(use_console); |
| if (!subprocess->Start(this, command)) { |
| delete subprocess; |
| return 0; |
| } |
| if (subprocess->child_) |
| running_.push_back(subprocess); |
| else |
| finished_.push(subprocess); |
| return subprocess; |
| } |
| |
| bool SubprocessSet::DoWork() { |
| DWORD bytes_read; |
| Subprocess* subproc; |
| OVERLAPPED* overlapped; |
| |
| if (!GetQueuedCompletionStatus(ioport_, &bytes_read, (PULONG_PTR)&subproc, |
| &overlapped, INFINITE)) { |
| if (GetLastError() != ERROR_BROKEN_PIPE) |
| Win32Fatal("GetQueuedCompletionStatus"); |
| } |
| |
| if (!subproc) // A NULL subproc indicates that we were interrupted and is |
| // delivered by NotifyInterrupted above. |
| return true; |
| |
| subproc->OnPipeReady(); |
| |
| if (subproc->Done()) { |
| vector<Subprocess*>::iterator end = |
| remove(running_.begin(), running_.end(), subproc); |
| if (running_.end() != end) { |
| finished_.push(subproc); |
| running_.resize(end - running_.begin()); |
| } |
| } |
| |
| return false; |
| } |
| |
| Subprocess* SubprocessSet::NextFinished() { |
| if (finished_.empty()) |
| return NULL; |
| Subprocess* subproc = finished_.front(); |
| finished_.pop(); |
| return subproc; |
| } |
| |
| void SubprocessSet::Clear() { |
| for (vector<Subprocess*>::iterator i = running_.begin(); |
| i != running_.end(); ++i) { |
| // 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 ((*i)->child_ && !(*i)->use_console_) { |
| if (!GenerateConsoleCtrlEvent(CTRL_BREAK_EVENT, |
| GetProcessId((*i)->child_))) { |
| Win32Fatal("GenerateConsoleCtrlEvent"); |
| } |
| } |
| } |
| for (vector<Subprocess*>::iterator i = running_.begin(); |
| i != running_.end(); ++i) |
| delete *i; |
| running_.clear(); |
| } |