aboutsummaryrefslogtreecommitdiffhomepage
path: root/src/main/java/com/google/devtools/build/lib/windows/jni
diff options
context:
space:
mode:
authorGravatar Laszlo Csomor <laszlocsomor@google.com>2017-06-30 16:22:41 +0200
committerGravatar Marcel Hlopko <hlopko@google.com>2017-07-03 09:05:27 +0200
commit13f9226911981339225456f38f74d04c7bcbd92b (patch)
treee1f5fbbc470695ad6a28bc3cc19a26e233f11e43 /src/main/java/com/google/devtools/build/lib/windows/jni
parent59a0e4f10bf037dcd4f257b2e21573fa1b690e93 (diff)
Windows, JNI: move around sources
Move the Java JNI sources to a separate package: c.g.devtools.build.lib.windows.jni and c.g.devtools.build.lib.windows.runfiles. Make the native method declarations private, create public wrapper methods for them that ensure that the JNI library is loaded. Split the C++ JNI source processes.cc into two parts (processes-jni.cc and file-jni.cc), extract common functionality to jni-util.{h,cc}. This change preparse the code for Android rule support on Windows, specifically it lets the Android BusyBox use the file JNI library so it can create junctions on Windows to work around long path issues when calling external tools. See https://github.com/bazelbuild/bazel/issues/3264 Change-Id: I7f1a746d73f822ae419d11b893a91f4eb45d64da PiperOrigin-RevId: 160643355
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();
+ }
+}