// 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.jni; import java.util.List; /** Process management on Windows. */ public class WindowsProcesses { public static final long INVALID = -1; private WindowsProcesses() { // Prevent construction } /** * Creates a process with the specified Windows command line. * *

Appropriately quoting arguments is the responsibility of the caller. * * @param argv0 the binary to run; must be unquoted; must be either an absolute, normalized * Windows path with a drive letter (e.g. "c:\foo\bar app.exe") or a single file name (e.g. * "foo app.exe") * @param argvRest the rest of the command line, i.e. argv[1:] (needs to be quoted Windows style) * @param env the environment of the new process. null means inherit that of the Bazel server * @param cwd the working directory of the new process. if null, the same as that of the current * process * @param stdoutFile the file the stdout should be redirected to. if null, nativeReadStdout will * work. * @param stderrFile the file the stdout should be redirected to. if null, nativeReadStderr will * work. * @param redirectErrorStream whether we merge the process's standard error and standard output. * @return the opaque identifier of the created process */ public static long createProcess( String argv0, String argvRest, byte[] env, String cwd, String stdoutFile, String stderrFile, boolean redirectErrorStream) { WindowsJniLoader.loadJni(); return nativeCreateProcess( argv0, argvRest, env, cwd, stdoutFile, stderrFile, redirectErrorStream); } public static long createProcess( String argv0, String argvRest, byte[] env, String cwd, String stdoutFile, String stderrFile) { WindowsJniLoader.loadJni(); return nativeCreateProcess(argv0, argvRest, env, cwd, stdoutFile, stderrFile, false); } private static native long nativeCreateProcess( String argv0, String argvRest, byte[] env, String cwd, String stdoutFile, String stderrFile, boolean redirectErrorStream); /** * Writes data from the given array to the stdin of the specified process. * *

Blocks until either some data was written or the process is terminated. * * @return the number of bytes written */ public static int writeStdin(long process, byte[] bytes, int offset, int length) { WindowsJniLoader.loadJni(); return nativeWriteStdin(process, bytes, offset, length); } private static native int nativeWriteStdin(long process, byte[] bytes, int offset, int length); /** Returns an opaque identifier of stdout stream for the process. */ public static long getStdout(long process) { WindowsJniLoader.loadJni(); return nativeGetStdout(process); } private static native long nativeGetStdout(long process); /** Returns an opaque identifier of stderr stream for the process. */ public static long getStderr(long process) { WindowsJniLoader.loadJni(); return nativeGetStderr(process); } private static native long nativeGetStderr(long process); /** * Reads data from the stream into the given array. {@code stream} should come from {@link * #nativeGetStdout(long)} or {@link #nativeGetStderr(long)}. * *

Blocks until either some data was read or the process is terminated. * * @return the number of bytes read, 0 on EOF, or -1 if there was an error. */ public static int readStream(long stream, byte[] bytes, int offset, int length) { WindowsJniLoader.loadJni(); return nativeReadStream(stream, bytes, offset, length); } private static native int nativeReadStream(long stream, byte[] bytes, int offset, int length); /** * Waits until the given process terminates. If timeout is non-negative, it indicates the number * of milliseconds before the call times out. * *

Return values: *

  • 0: Process finished *
  • 1: Timeout *
  • 2: Something went wrong */ public static int waitFor(long process, long timeout) { WindowsJniLoader.loadJni(); return nativeWaitFor(process, timeout); } private static native int nativeWaitFor(long process, long timeout); /** * Returns the exit code of the process. Throws {@code IllegalStateException} if something goes * wrong. */ public static int getExitCode(long process) { WindowsJniLoader.loadJni(); return nativeGetExitCode(process); } private static native int nativeGetExitCode(long process); /** Returns the process ID of the given process or -1 if there was an error. */ public static int getProcessPid(long process) { WindowsJniLoader.loadJni(); return nativeGetProcessPid(process); } private static native int nativeGetProcessPid(long process); /** Terminates the given process. Returns true if the termination was successful. */ public static boolean terminate(long process) { WindowsJniLoader.loadJni(); return nativeTerminate(process); } private static native boolean nativeTerminate(long process); /** * Releases the native data structures associated with the process. * *

    Calling any other method on the same process after this call will result in the JVM crashing * or worse. */ public static void deleteProcess(long process) { WindowsJniLoader.loadJni(); nativeDeleteProcess(process); } private static native void nativeDeleteProcess(long process); /** * Closes the stream * * @param stream should come from {@link #nativeGetStdout(long)} or {@link * #nativeGetStderr(long)}. */ public static void closeStream(long stream) { WindowsJniLoader.loadJni(); nativeCloseStream(stream); } private static native void nativeCloseStream(long stream); /** * Returns a string representation of the last error caused by any call on the given process or * the empty string if the last operation was successful. * *

    Does NOT terminate the process if it is still running. * *

    After this call returns, subsequent calls will return the empty string if there was no * failed operation in between. */ public static String processGetLastError(long process) { WindowsJniLoader.loadJni(); return nativeProcessGetLastError(process); } private static native String nativeProcessGetLastError(long process); public static String streamGetLastError(long process) { WindowsJniLoader.loadJni(); return nativeStreamGetLastError(process); } private static native String nativeStreamGetLastError(long process); /** returns the PID of the current process. */ public static int getpid() { WindowsJniLoader.loadJni(); return nativeGetpid(); } private static native int nativeGetpid(); // TODO(laszlocsomor): Audit this method and fix bugs. This method implements Bash quoting // semantics but Windows quote semantics are different. // More info: http://daviddeley.com/autohotkey/parameters/parameters.htm public static String quoteCommandLine(List argv) { StringBuilder result = new StringBuilder(); for (int iArg = 0; iArg < argv.size(); iArg++) { if (iArg != 0) { result.append(" "); } String arg = argv.get(iArg); if (arg.isEmpty()) { result.append("\"\""); continue; } boolean hasSpace = arg.contains(" "); if (!arg.contains("\"") && !arg.contains("\\") && !hasSpace) { // fast path. Just append the input string. result.append(arg); } else { // We need to quote things if the argument contains a space. if (hasSpace) { result.append("\""); } for (int iChar = 0; iChar < arg.length(); iChar++) { boolean lastChar = iChar == arg.length() - 1; switch (arg.charAt(iChar)) { case '"': // Escape double quotes result.append("\\\""); break; case '\\': // Backslashes at the end of the string are quoted if we add quotes if (lastChar) { result.append(hasSpace ? "\\\\" : "\\"); } else { // Backslashes everywhere else are quoted if they are followed by a // quote or a backslash result.append( arg.charAt(iChar + 1) == '"' || arg.charAt(iChar + 1) == '\\' ? "\\\\" : "\\"); } break; default: result.append(arg.charAt(iChar)); } } // Add ending quotes if we added a quote at the beginning. if (hasSpace) { result.append("\""); } } } return result.toString(); } }