summaryrefslogtreecommitdiffstats
path: root/src/subprocess-win32.cc
diff options
context:
space:
mode:
authorEvan Martin <martine@danga.com>2011-05-06 18:46:11 (GMT)
committerEvan Martin <martine@danga.com>2011-05-06 19:21:06 (GMT)
commit6cf3f79fb45e196bd47ea25f9c030c3364494ebc (patch)
treead165d26cee376a8383b3ffa44acbb31cad4626c /src/subprocess-win32.cc
parent661c5435e8bf108ded8ba795cc0a3aafda03b791 (diff)
downloadNinja-6cf3f79fb45e196bd47ea25f9c030c3364494ebc.zip
Ninja-6cf3f79fb45e196bd47ea25f9c030c3364494ebc.tar.gz
Ninja-6cf3f79fb45e196bd47ea25f9c030c3364494ebc.tar.bz2
windows: subprocess implementation for Windows
Heavily based on a patch from Sergey Nenakhov <nenakhov.sergey@gmail.com>.
Diffstat (limited to 'src/subprocess-win32.cc')
-rw-r--r--src/subprocess-win32.cc211
1 files changed, 211 insertions, 0 deletions
diff --git a/src/subprocess-win32.cc b/src/subprocess-win32.cc
new file mode 100644
index 0000000..2efec00
--- /dev/null
+++ b/src/subprocess-win32.cc
@@ -0,0 +1,211 @@
+// Copyright 2011 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 <stdio.h>
+#include <windows.h>
+
+#include <algorithm>
+
+#include "util.h"
+
+namespace {
+
+void Win32Fatal(const char* function) {
+ DWORD err = GetLastError();
+
+ char* msg_buf;
+ FormatMessageA(
+ FORMAT_MESSAGE_ALLOCATE_BUFFER |
+ FORMAT_MESSAGE_FROM_SYSTEM |
+ FORMAT_MESSAGE_IGNORE_INSERTS,
+ NULL,
+ err,
+ MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
+ (char*)&msg_buf,
+ 0,
+ NULL);
+ Fatal("%s: %s", function, msg_buf);
+ LocalFree(msg_buf);
+}
+
+} // anonymous namespace
+
+Subprocess::Subprocess() : child_(NULL) , overlapped_() {
+}
+
+Subprocess::~Subprocess() {
+ // Reap child if forgotten.
+ if (child_)
+ Finish();
+}
+
+HANDLE Subprocess::SetupPipe(HANDLE ioport) {
+ char pipe_name[32];
+ snprintf(pipe_name, sizeof(pipe_name),
+ "\\\\.\\pipe\\ninja_%p_out", ::GetModuleHandle(NULL));
+
+ 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 = CreateFile(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(struct SubprocessSet* set, const string& command) {
+ HANDLE child_pipe = SetupPipe(set->ioport_);
+
+ STARTUPINFOA startup_info = {};
+ startup_info.cb = sizeof(STARTUPINFO);
+ startup_info.dwFlags = STARTF_USESTDHANDLES;
+ startup_info.hStdOutput = child_pipe;
+ startup_info.hStdInput = NULL;
+ startup_info.hStdError = NULL; // TODO: handle child stderr as well.
+
+ PROCESS_INFORMATION process_info;
+
+ string full_command = "cmd /c " + command;
+ printf("running %s\n", full_command.c_str());
+ if (!CreateProcessA(NULL, (char*)full_command.c_str(), NULL, NULL,
+ /* inherit handles */ TRUE, 0,
+ NULL, NULL,
+ &startup_info, &process_info)) {
+ Win32Fatal("CreateProcess");
+ }
+
+ // Close pipe channel only used by the child.
+ if (child_pipe)
+ CloseHandle(child_pipe);
+
+ CloseHandle(process_info.hThread);
+ child_ = process_info.hProcess;
+
+ return true;
+}
+
+void Subprocess::OnPipeReady() {
+ DWORD bytes;
+ if (!GetOverlappedResult(pipe_, &overlapped_, &bytes, FALSE)) {
+ if (GetLastError() == ERROR_BROKEN_PIPE) {
+ CloseHandle(pipe_);
+ pipe_ = NULL;
+ return;
+ }
+ Win32Fatal("GetOverlappedResult");
+ }
+
+ if (bytes)
+ buf_.append(overlapped_buf_, bytes);
+
+ memset(&overlapped_, 0, sizeof(overlapped_));
+ if (!::ReadFile(pipe_, overlapped_buf_, sizeof(overlapped_buf_),
+ &bytes, &overlapped_)) {
+ 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.
+}
+
+bool Subprocess::Finish() {
+ // 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;
+}
+
+bool Subprocess::Done() const {
+ return pipe_ == NULL;
+}
+
+const string& Subprocess::GetOutput() const {
+ return buf_;
+}
+
+SubprocessSet::SubprocessSet() {
+ ioport_ = ::CreateIoCompletionPort(INVALID_HANDLE_VALUE, NULL, 0, 1);
+ if (!ioport_)
+ Win32Fatal("CreateIoCompletionPort");
+}
+
+SubprocessSet::~SubprocessSet() {
+ CloseHandle(ioport_);
+}
+
+void SubprocessSet::Add(Subprocess* subprocess) {
+ running_.push_back(subprocess);
+}
+
+void 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");
+ }
+
+ subproc->OnPipeReady();
+
+ if (subproc->Done()) {
+ vector<Subprocess*>::iterator end =
+ std::remove(running_.begin(), running_.end(), subproc);
+ if (running_.end() != end) {
+ finished_.push(subproc);
+ running_.resize(end - running_.begin());
+ }
+ }
+}
+
+Subprocess* SubprocessSet::NextFinished() {
+ if (finished_.empty())
+ return NULL;
+ Subprocess* subproc = finished_.front();
+ finished_.pop();
+ return subproc;
+}