|  | // Copyright 2016 The Bazel Authors. 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. | 
|  |  | 
|  | package com.google.devtools.build.lib.windows; | 
|  |  | 
|  | import com.google.common.base.Throwables; | 
|  | import com.google.devtools.build.lib.shell.Subprocess; | 
|  | import com.google.devtools.build.lib.windows.jni.WindowsProcesses; | 
|  | import java.io.IOException; | 
|  | import java.io.InputStream; | 
|  | import java.io.OutputStream; | 
|  | import java.util.concurrent.ExecutionException; | 
|  | import java.util.concurrent.ExecutorService; | 
|  | import java.util.concurrent.Executors; | 
|  | import java.util.concurrent.Future; | 
|  | import java.util.concurrent.ThreadFactory; | 
|  | import java.util.concurrent.atomic.AtomicInteger; | 
|  |  | 
|  | /** | 
|  | * A Windows subprocess backed by a native object. | 
|  | */ | 
|  | public class WindowsSubprocess implements Subprocess { | 
|  | // For debugging purposes. | 
|  | private String commandLine; | 
|  |  | 
|  | private static enum WaitResult { | 
|  | SUCCESS, | 
|  | TIMEOUT | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Output stream for writing to the stdin of a Windows process. | 
|  | */ | 
|  | private class ProcessOutputStream extends OutputStream { | 
|  | private ProcessOutputStream() { | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public void write(int b) throws IOException { | 
|  | byte[] buf = new byte[]{ (byte) b }; | 
|  | write(buf, 0, 1); | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public void write(byte[] b, int off, int len) throws IOException { | 
|  | writeStream(b, off, len); | 
|  | } | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Input stream for reading the stdout or stderr of a Windows process. | 
|  | * | 
|  | * <p>This class is non-static for debugging purposes. | 
|  | */ | 
|  | private static final class ProcessInputStream extends InputStream { | 
|  | private long nativeStream; | 
|  |  | 
|  | ProcessInputStream(long nativeStream) { | 
|  | this.nativeStream = nativeStream; | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public int read() throws IOException { | 
|  | byte[] buf = new byte[1]; | 
|  | if (read(buf, 0, 1) != 1) { | 
|  | return -1; | 
|  | } else { | 
|  | return buf[0] & 0xff; | 
|  | } | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public synchronized int read(byte[] b, int off, int len) throws IOException { | 
|  | if (nativeStream == WindowsProcesses.INVALID) { | 
|  | throw new IllegalStateException(); | 
|  | } | 
|  |  | 
|  | int result = WindowsProcesses.readStream(nativeStream, b, off, len); | 
|  |  | 
|  | if (result == 0) { | 
|  | return -1; // EOF | 
|  | } | 
|  | if (result == -1) { | 
|  | throw new IOException(WindowsProcesses.streamGetLastError(nativeStream)); | 
|  | } | 
|  |  | 
|  | return result; | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public synchronized void close() { | 
|  | if (nativeStream != WindowsProcesses.INVALID) { | 
|  | WindowsProcesses.closeStream(nativeStream); | 
|  | nativeStream = WindowsProcesses.INVALID; | 
|  | } | 
|  | } | 
|  |  | 
|  | @Override | 
|  | protected void finalize() throws Throwable { | 
|  | close(); | 
|  | super.finalize(); | 
|  | } | 
|  | } | 
|  |  | 
|  | private static final AtomicInteger THREAD_SEQUENCE_NUMBER = new AtomicInteger(1); | 
|  | private static final ExecutorService WAITER_POOL = Executors.newCachedThreadPool( | 
|  | new ThreadFactory() { | 
|  | @Override | 
|  | public Thread newThread(Runnable runnable) { | 
|  | Thread thread = new Thread(null, runnable, | 
|  | "Windows-Process-Waiter-Thread-" + THREAD_SEQUENCE_NUMBER.getAndIncrement(), | 
|  | 16 * 1024); | 
|  | thread.setDaemon(true); | 
|  | return thread; | 
|  | } | 
|  | }); | 
|  |  | 
|  | private volatile long nativeProcess; | 
|  | private final OutputStream stdinStream; | 
|  | private final ProcessInputStream stdoutStream; | 
|  | private final ProcessInputStream stderrStream; | 
|  | private final Future<WaitResult> processFuture; | 
|  | private final long timeoutMillis; | 
|  | private boolean timedout = false; | 
|  |  | 
|  | WindowsSubprocess(long nativeProcess, String commandLine, boolean stdoutRedirected, | 
|  | boolean stderrRedirected, long timeoutMillis) { | 
|  | this.commandLine = commandLine; | 
|  | this.nativeProcess = nativeProcess; | 
|  | // As per the spec of Command, we should only apply timeouts that are > 0. | 
|  | this.timeoutMillis = timeoutMillis <= 0 ? -1 : timeoutMillis; | 
|  | stdoutStream = | 
|  | stdoutRedirected ? null : new ProcessInputStream(WindowsProcesses.getStdout(nativeProcess)); | 
|  | stderrStream = | 
|  | stderrRedirected ? null : new ProcessInputStream(WindowsProcesses.getStderr(nativeProcess)); | 
|  | stdinStream = new ProcessOutputStream(); | 
|  | // Every Windows process we start consumes a thread here. This is suboptimal, but seems to be | 
|  | // the sanest way to reconcile WaitForMultipleObjects() and Java-style interruption. | 
|  | processFuture = WAITER_POOL.submit(this::waiterThreadFunc); | 
|  | } | 
|  |  | 
|  | // Waits for the process to finish. | 
|  | private WaitResult waiterThreadFunc() { | 
|  | switch (WindowsProcesses.waitFor(nativeProcess, timeoutMillis)) { | 
|  | case 0: | 
|  | // Excellent, process finished in time. | 
|  | return WaitResult.SUCCESS; | 
|  |  | 
|  | case 1: | 
|  | // Timeout. We don't need to call `terminate` here, because waitFor | 
|  | // automatically terminates the process in case of a timeout. | 
|  | return WaitResult.TIMEOUT; | 
|  |  | 
|  | default: | 
|  | // Error. There isn't a lot we can do -- the process is still alive but | 
|  | // WaitForSingleObject() failed for some odd reason. This should | 
|  | // basically never happen, but if it does... let's get a stack trace. | 
|  | String errorMessage = WindowsProcesses.processGetLastError(nativeProcess); | 
|  | throw new IllegalStateException( | 
|  | "Waiting for process " | 
|  | + WindowsProcesses.getProcessPid(nativeProcess) | 
|  | + " failed: " | 
|  | + errorMessage); | 
|  | } | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public synchronized void finalize() throws Throwable { | 
|  | if (nativeProcess != WindowsProcesses.INVALID) { | 
|  | close(); | 
|  | } | 
|  | super.finalize(); | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public synchronized boolean destroy() { | 
|  | checkLiveness(); | 
|  | return WindowsProcesses.terminate(nativeProcess); | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public synchronized int exitValue() { | 
|  | checkLiveness(); | 
|  |  | 
|  | int result = WindowsProcesses.getExitCode(nativeProcess); | 
|  | String error = WindowsProcesses.processGetLastError(nativeProcess); | 
|  | if (!error.isEmpty()) { | 
|  | throw new IllegalStateException(error); | 
|  | } | 
|  |  | 
|  | return result; | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public boolean finished() { | 
|  | return processFuture.isDone(); | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public boolean timedout() { | 
|  | return timedout; | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public void waitFor() throws InterruptedException { | 
|  | try { | 
|  | timedout = processFuture.get() == WaitResult.TIMEOUT; | 
|  | } catch (ExecutionException e) { | 
|  | Throwables.throwIfUnchecked(e.getCause()); | 
|  | // This should never happen, because waiterThreadFunc does not throw any | 
|  | // checked exceptions. | 
|  | throw new IllegalStateException("Unexpected exception", e); | 
|  | } | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public synchronized void close() { | 
|  | if (nativeProcess != WindowsProcesses.INVALID) { | 
|  | stdoutStream.close(); | 
|  | stderrStream.close(); | 
|  | long process = nativeProcess; | 
|  | nativeProcess = WindowsProcesses.INVALID; | 
|  | WindowsProcesses.deleteProcess(process); | 
|  | } | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public OutputStream getOutputStream() { | 
|  | return stdinStream; | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public InputStream getInputStream() { | 
|  | return stdoutStream; | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public InputStream getErrorStream() { | 
|  | return stderrStream; | 
|  | } | 
|  |  | 
|  | private synchronized void writeStream(byte[] b, int off, int len) throws IOException { | 
|  | checkLiveness(); | 
|  |  | 
|  | int remaining = len; | 
|  | int currentOffset = off; | 
|  | while (remaining != 0) { | 
|  | int written = WindowsProcesses.writeStdin(nativeProcess, b, currentOffset, remaining); | 
|  | // I think the Windows API never returns 0 in dwNumberOfBytesWritten | 
|  | // Verify.verify(written != 0); | 
|  | if (written == -1) { | 
|  | throw new IOException(WindowsProcesses.processGetLastError(nativeProcess)); | 
|  | } | 
|  |  | 
|  | remaining -= written; | 
|  | currentOffset += written; | 
|  | } | 
|  | } | 
|  |  | 
|  | private void checkLiveness() { | 
|  | if (nativeProcess == WindowsProcesses.INVALID) { | 
|  | throw new IllegalStateException(); | 
|  | } | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public String toString() { | 
|  | return String.format("%s:[%s]", super.toString(), commandLine); | 
|  | } | 
|  | } |