aboutsummaryrefslogtreecommitdiffhomepage
path: root/src/main/java/com/google/devtools/build/lib/windows/jni
diff options
context:
space:
mode:
Diffstat (limited to 'src/main/java/com/google/devtools/build/lib/windows/jni')
-rw-r--r--src/main/java/com/google/devtools/build/lib/windows/jni/BUILD37
-rw-r--r--src/main/java/com/google/devtools/build/lib/windows/jni/WindowsFileOperations.java124
-rw-r--r--src/main/java/com/google/devtools/build/lib/windows/jni/WindowsJniLoader.java42
-rw-r--r--src/main/java/com/google/devtools/build/lib/windows/jni/WindowsProcesses.java248
4 files changed, 451 insertions, 0 deletions
diff --git a/src/main/java/com/google/devtools/build/lib/windows/jni/BUILD b/src/main/java/com/google/devtools/build/lib/windows/jni/BUILD
new file mode 100644
index 0000000000..ed8ca69799
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/windows/jni/BUILD
@@ -0,0 +1,37 @@
+package(
+ default_visibility = [
+ "//src/main/java/com/google/devtools/build/lib:__subpackages__",
+ "//src/test/java/com/google/devtools/build/lib:__subpackages__",
+ ],
+)
+
+filegroup(
+ name = "srcs",
+ srcs = glob(["**"]),
+)
+
+java_library(
+ name = "jni",
+ exports = [
+ ":file",
+ ":processes",
+ ],
+)
+
+java_library(
+ name = "file",
+ srcs = ["WindowsFileOperations.java"],
+ deps = [":jni-loader"],
+)
+
+java_library(
+ name = "processes",
+ srcs = ["WindowsProcesses.java"],
+ deps = [":jni-loader"],
+)
+
+java_library(
+ name = "jni-loader",
+ srcs = ["WindowsJniLoader.java"],
+ deps = ["//src/main/java/com/google/devtools/build/lib/windows/runfiles"],
+)
diff --git a/src/main/java/com/google/devtools/build/lib/windows/jni/WindowsFileOperations.java b/src/main/java/com/google/devtools/build/lib/windows/jni/WindowsFileOperations.java
new file mode 100644
index 0000000000..44b6cba976
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/windows/jni/WindowsFileOperations.java
@@ -0,0 +1,124 @@
+// 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.io.IOException;
+
+/** File operations on Windows. */
+public class WindowsFileOperations {
+
+ // A note about UNC paths and path prefixes on Windows. The prefixes can be:
+ // - "\\?\", meaning it's a UNC path that is passed to user mode unicode WinAPI functions
+ // (e.g. CreateFileW) or a return value of theirs (e.g. GetLongPathNameW); this is the
+ // prefix we'll most often see
+ // - "\??\", meaning it's Device Object path; it's mostly only used by kernel/driver functions
+ // but we may come across it when resolving junction targets, as the target's path is
+ // specified with this prefix, see usages of DeviceIoControl with FSCTL_GET_REPARSE_POINT
+ // - "\\.\", meaning it's a Device Object path again; both "\??\" and "\\.\" are shorthands
+ // for the "\DosDevices\" Object Directory, so "\\.\C:" and "\??\C:" and "\DosDevices\C:"
+ // and "C:\" all mean the same thing, but functions like CreateFileW don't understand the
+ // fully qualified device path, only the shorthand versions; the difference between "\\.\"
+ // is "\??\" is not entirely clear (one is not available while Windows is booting, but
+ // that only concerns device drivers) but we most likely won't come across them anyway
+ // Some of this is documented here:
+ // - https://msdn.microsoft.com/en-us/library/windows/hardware/ff557762(v=vs.85).aspx
+ // - https://msdn.microsoft.com/en-us/library/windows/hardware/ff565384(v=vs.85).aspx
+ // - http://stackoverflow.com/questions/23041983
+ // - http://stackoverflow.com/questions/14482421
+
+ private WindowsFileOperations() {
+ // Prevent construction
+ }
+
+ private static final int MAX_PATH = 260;
+
+ // Keep IS_JUNCTION_* values in sync with src/main/native/windows_file_operations.cc.
+ private static final int IS_JUNCTION_YES = 0;
+ private static final int IS_JUNCTION_NO = 1;
+ private static final int IS_JUNCTION_ERROR = 2;
+
+ private static native int nativeIsJunction(String path, String[] error);
+
+ private static native boolean nativeGetLongPath(String path, String[] result, String[] error);
+
+ private static native boolean nativeCreateJunction(String name, String target, String[] error);
+
+ /** Determines whether `path` is a junction point or directory symlink. */
+ public static boolean isJunction(String path) throws IOException {
+ WindowsJniLoader.loadJni();
+ String[] error = new String[] {null};
+ switch (nativeIsJunction(asLongPath(path), error)) {
+ case IS_JUNCTION_YES:
+ return true;
+ case IS_JUNCTION_NO:
+ return false;
+ default:
+ throw new IOException(error[0]);
+ }
+ }
+
+ /**
+ * Returns the long path associated with the input `path`.
+ *
+ * <p>This method resolves all 8dot3 style components of the path and returns the long format. For
+ * example, if the input is "C:/progra~1/micros~1" the result may be "C:\Program Files\Microsoft
+ * Visual Studio 14.0". The returned path is Windows-style in that it uses backslashes, even if
+ * the input uses forward slashes.
+ *
+ * <p>May return an UNC path if `path` or its resolution is sufficiently long.
+ *
+ * @throws IOException if the `path` is not found or some other I/O error occurs
+ */
+ public static String getLongPath(String path) throws IOException {
+ WindowsJniLoader.loadJni();
+ String[] result = new String[] {null};
+ String[] error = new String[] {null};
+ if (nativeGetLongPath(asLongPath(path), result, error)) {
+ return result[0];
+ } else {
+ throw new IOException(error[0]);
+ }
+ }
+
+ /**
+ * Returns a Windows-style path suitable to pass to unicode WinAPI functions.
+ *
+ * <p>Returns an UNC path if `path` is at least `MAX_PATH` long. If it's shorter or is already an
+ * UNC path, then this method returns `path` itself.
+ */
+ static String asLongPath(String path) {
+ return path.length() >= MAX_PATH && !path.startsWith("\\\\?\\")
+ ? ("\\\\?\\" + path.replace('/', '\\'))
+ : path;
+ }
+
+ /**
+ * Creates a junction at `name`, pointing to `target`.
+ *
+ * <p>Both `name` and `target` may be Unix-style Windows paths (i.e. use forward slashes), and
+ * they don't need to have a UNC prefix, not even if they are longer than `MAX_PATH`. The
+ * underlying logic will take care of adding the prefixes if necessary.
+ *
+ * @throws IOException if some error occurs
+ */
+ public static void createJunction(String name, String target) throws IOException {
+ WindowsJniLoader.loadJni();
+ String[] error = new String[] {null};
+ if (!nativeCreateJunction(name.replace('/', '\\'), target.replace('/', '\\'), error)) {
+ throw new IOException(
+ String.format("Cannot create junction (name=%s, target=%s): %s", name, target, error[0]));
+ }
+ }
+}
diff --git a/src/main/java/com/google/devtools/build/lib/windows/jni/WindowsJniLoader.java b/src/main/java/com/google/devtools/build/lib/windows/jni/WindowsJniLoader.java
new file mode 100644
index 0000000000..8b53d0c1a8
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/windows/jni/WindowsJniLoader.java
@@ -0,0 +1,42 @@
+// 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 com.google.devtools.build.lib.windows.runfiles.WindowsRunfiles;
+import java.io.IOException;
+
+/** Loads native code under Windows. */
+public class WindowsJniLoader {
+ private static boolean jniLoaded = false;
+
+ public static synchronized void loadJni() {
+ if (jniLoaded) {
+ return;
+ }
+
+ try {
+ System.loadLibrary("windows_jni");
+ } catch (UnsatisfiedLinkError ex) {
+ // We are probably in tests, let's try to find the library in the runfiles
+ try {
+ System.load(WindowsRunfiles.getRunfile("io_bazel/src/main/native/windows_jni.dll"));
+ } catch (IOException e) {
+ // We throw the UnsatisfiedLinkError if we cannot find the runfiles
+ throw ex;
+ }
+ }
+ jniLoaded = true;
+ }
+}
diff --git a/src/main/java/com/google/devtools/build/lib/windows/jni/WindowsProcesses.java b/src/main/java/com/google/devtools/build/lib/windows/jni/WindowsProcesses.java
new file mode 100644
index 0000000000..1eaab25904
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/windows/jni/WindowsProcesses.java
@@ -0,0 +1,248 @@
+// 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.
+ *
+ * <p>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 commandLine the command line (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.
+ * @return the opaque identifier of the created process
+ */
+ 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);
+ }
+
+ private static native long nativeCreateProcess(
+ String argv0, String argvRest, byte[] env, String cwd, String stdoutFile, String stderrFile);
+
+ /**
+ * Writes data from the given array to the stdin of the specified process.
+ *
+ * <p>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)}.
+ *
+ * <p>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.
+ *
+ * <p>Return values:
+ * <li>0: Process finished
+ * <li>1: Timeout
+ * <li>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.
+ *
+ * <p>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.
+ *
+ * <p>Does <b>NOT</b> terminate the process if it is still running.
+ *
+ * <p>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();
+
+ public static String quoteCommandLine(List<String> argv) {
+ StringBuilder result = new StringBuilder();
+ for (int iArg = 0; iArg < argv.size(); iArg++) {
+ if (iArg != 0) {
+ result.append(" ");
+ }
+ String arg = argv.get(iArg);
+ 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();
+ }
+}