Repository: bazelbuild/bazel
Issue: Windows: Path.readSymbolicLink fails for junctions
Labels: type: bug
P2
area-Windows
team-OSS

### Description of the problem / feature request:

Path.readSymbolicLink calls FileSystem.readSymbolicLink:
https://github.com/bazelbuild/bazel/blob/d7aa53ec68f573f9f130be18ac3ac1350d1d6be6/src/main/java/com/google/devtools/build/lib/vfs/Path.java#L599

On Windows, this calls into JavaIoFileSystem, which calls java.nio.file.Files.readSymbolicLink, which is unaware of junctions.

### Bugs: what's the simplest, easiest way to reproduce this bug? Please provide a minimal example if possible.

The bug is rooted in java.nio, so here's an out-of-bazel repro:

BUILD:
```
java_binary(
    name = "x",
    srcs = ["ReadJunc.java"],
    main_class = "ReadJunc",
)
```

ReadJunc.java:
```
import java.nio.file.Files;
import java.nio.file.FileSystems;
import java.nio.file.Path;

public class ReadJunc {
  public static void main(String[] args) throws Exception {
    System.err.println("Hello ReadJunc");

    Path path = FileSystems.getDefault().getPath("c:\\src\\bazel-releases\\current");
    System.err.printf("path=(%s) isdir=%d%n",
        path, path.toFile().isDirectory() ? 1 : 0);

    Path path2 = Files.readSymbolicLink(path);
    System.err.printf("path2=(%s)%n", path2);
  }
}
```

output:
```
C:\src\tmp>bazel run x
...
path=(c:\src\bazel-releases\current) isdir=1
Exception in thread "main" java.nio.file.NotLinkException: Reparse point is not a symbolic link
        at sun.nio.fs.WindowsLinkSupport.readLinkImpl(WindowsLinkSupport.java:318)
        at sun.nio.fs.WindowsLinkSupport.readLink(WindowsLinkSupport.java:59)
        at sun.nio.fs.WindowsFileSystemProvider.readSymbolicLink(WindowsFileSystemProvider.java:628)
        at java.nio.file.Files.readSymbolicLink(Files.java:1432)
        at ReadJunc.main(ReadJunc.java:13)
```

The particular path:
```
C:\src\tmp>dir c:\src\bazel-releases
...
2019-03-27  11:12    <JUNCTION>     current [C:\src\bazel-releases\0.24.0]
```

### What operating system are you running Bazel on?

Windows 10

Issue comments:

Pull request review comments:

Patch that resolved the issue:
diff --git a/src/main/java/com/google/devtools/build/lib/windows/WindowsFileSystem.java b/src/main/java/com/google/devtools/build/lib/windows/WindowsFileSystem.java
index ee457b70652b5d..5b3669b8ed5a8c 100644
--- a/src/main/java/com/google/devtools/build/lib/windows/WindowsFileSystem.java
+++ b/src/main/java/com/google/devtools/build/lib/windows/WindowsFileSystem.java
@@ -108,7 +108,7 @@ public boolean isFilePathCaseSensitive() {
   @Override
   protected boolean fileIsSymbolicLink(File file) {
     try {
-      if (isJunction(file)) {
+      if (isSymlinkOrJunction(file)) {
         return true;
       }
     } catch (IOException e) {
@@ -184,7 +184,7 @@ public long getNodeId() {
   protected boolean isDirectory(Path path, boolean followSymlinks) {
     if (!followSymlinks) {
       try {
-        if (isJunction(getIoFile(path))) {
+        if (isSymlinkOrJunction(getIoFile(path))) {
           return false;
         }
       } catch (IOException e) {
@@ -214,8 +214,8 @@ protected boolean isDirectory(Path path, boolean followSymlinks) {
    * they are dangling), though only directory junctions and directory symlinks are useful.
    */
   @VisibleForTesting
-  static boolean isJunction(File file) throws IOException {
-    return WindowsFileOperations.isJunction(file.getPath());
+  static boolean isSymlinkOrJunction(File file) throws IOException {
+    return WindowsFileOperations.isSymlinkOrJunction(file.getPath());
   }
 
   private static DosFileAttributes getAttribs(File file, boolean followSymlinks)
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
index 82086181f0d2f6..ba82a4f98bfb8b 100644
--- 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
@@ -44,10 +44,10 @@ private WindowsFileOperations() {
 
   private static final int MAX_PATH = 260;
 
-  // Keep IS_JUNCTION_* values in sync with src/main/native/windows/file.h.
-  private static final int IS_JUNCTION_YES = 0;
-  private static final int IS_JUNCTION_NO = 1;
-  // IS_JUNCTION_ERROR = 2;
+  // Keep IS_SYMLINK_OR_JUNCTION_* values in sync with src/main/native/windows/file.cc.
+  private static final int IS_SYMLINK_OR_JUNCTION_SUCCESS = 0;
+  // IS_SYMLINK_OR_JUNCTION_ERROR = 1;
+  private static final int IS_SYMLINK_OR_JUNCTION_DOES_NOT_EXIST = 2;
 
   // Keep CREATE_JUNCTION_* values in sync with src/main/native/windows/file.h.
   private static final int CREATE_JUNCTION_SUCCESS = 0;
@@ -73,7 +73,8 @@ private WindowsFileOperations() {
   private static final int READ_SYMLINK_OR_JUNCTION_NOT_A_LINK = 4;
   private static final int READ_SYMLINK_OR_JUNCTION_UNKNOWN_LINK_TYPE = 5;
 
-  private static native int nativeIsJunction(String path, String[] error);
+  private static native int nativeIsSymlinkOrJunction(
+      String path, boolean[] result, String[] error);
 
   private static native boolean nativeGetLongPath(String path, String[] result, String[] error);
 
@@ -85,18 +86,22 @@ private static native int nativeReadSymlinkOrJunction(
   private static native int nativeDeletePath(String path, String[] error);
 
   /** Determines whether `path` is a junction point or directory symlink. */
-  public static boolean isJunction(String path) throws IOException {
+  public static boolean isSymlinkOrJunction(String path) throws IOException {
     WindowsJniLoader.loadJni();
+    boolean[] result = new boolean[] {false};
     String[] error = new String[] {null};
-    switch (nativeIsJunction(asLongPath(path), error)) {
-      case IS_JUNCTION_YES:
-        return true;
-      case IS_JUNCTION_NO:
-        return false;
+    switch (nativeIsSymlinkOrJunction(asLongPath(path), result, error)) {
+      case IS_SYMLINK_OR_JUNCTION_SUCCESS:
+        return result[0];
+      case IS_SYMLINK_OR_JUNCTION_DOES_NOT_EXIST:
+        error[0] = "path does not exist";
+        break;
       default:
-        // This is IS_JUNCTION_ERROR. The JNI code puts a custom message in 'error[0]'.
-        throw new IOException(error[0]);
+        // This is IS_SYMLINK_OR_JUNCTION_ERROR (1). The JNI code puts a custom message in
+        // 'error[0]'.
+        break;
     }
+    throw new IOException(String.format("Cannot tell if '%s' is link: %s", path, error[0]));
   }
 
   /**
diff --git a/src/main/native/windows/file-jni.cc b/src/main/native/windows/file-jni.cc
index 3befe0fe27fcdc..b9be7ebe3bfa2a 100644
--- a/src/main/native/windows/file-jni.cc
+++ b/src/main/native/windows/file-jni.cc
@@ -40,20 +40,26 @@ static void ReportLastError(const std::wstring& error_str, JNIEnv* env,
 }
 
 extern "C" JNIEXPORT jint JNICALL
-Java_com_google_devtools_build_lib_windows_jni_WindowsFileOperations_nativeIsJunction(
-    JNIEnv* env, jclass clazz, jstring path, jobjectArray error_msg_holder) {
+Java_com_google_devtools_build_lib_windows_jni_WindowsFileOperations_nativeIsSymlinkOrJunction(
+    JNIEnv* env, jclass clazz, jstring path, jbooleanArray result_holder,
+    jobjectArray error_msg_holder) {
   std::wstring wpath(bazel::windows::GetJavaWstring(env, path));
   std::wstring error;
-  int result =
-      bazel::windows::IsJunctionOrDirectorySymlink(wpath.c_str(), &error);
-  if (result == bazel::windows::IS_JUNCTION_ERROR &&
-      CanReportError(env, error_msg_holder)) {
-    ReportLastError(
-        bazel::windows::MakeErrorMessage(WSTR(__FILE__), __LINE__,
-                                         L"nativeIsJunction", wpath, error),
-        env, error_msg_holder);
+  bool is_sym = false;
+  int result = bazel::windows::IsSymlinkOrJunction(wpath.c_str(), &is_sym,
+                                                   &error);
+  if (result == bazel::windows::IsSymlinkOrJunctionResult::kSuccess) {
+    jboolean is_sym_jbool = is_sym ? JNI_TRUE : JNI_FALSE;
+    env->SetBooleanArrayRegion(result_holder, 0, 1, &is_sym_jbool);
+  } else {
+    if (!error.empty() && CanReportError(env, error_msg_holder)) {
+      ReportLastError(
+          bazel::windows::MakeErrorMessage(WSTR(__FILE__), __LINE__,
+                                           L"nativeIsJunction", wpath, error),
+          env, error_msg_holder);
+    }
   }
-  return result;
+  return static_cast<jint>(result);
 }
 
 extern "C" JNIEXPORT jboolean JNICALL
diff --git a/src/main/native/windows/file.cc b/src/main/native/windows/file.cc
index e7cf81a78c4049..c3cbd63f62cad2 100644
--- a/src/main/native/windows/file.cc
+++ b/src/main/native/windows/file.cc
@@ -78,31 +78,31 @@ static wstring uint32asHexString(uint32_t value) {
   return wstring(attr_str, 8);
 }
 
-int IsJunctionOrDirectorySymlink(const WCHAR* path, wstring* error) {
+int IsSymlinkOrJunction(const WCHAR* path, bool* result, wstring* error) {
   if (!IsAbsoluteNormalizedWindowsPath(path)) {
     if (error) {
       *error = MakeErrorMessage(WSTR(__FILE__), __LINE__,
-                                L"IsJunctionOrDirectorySymlink", path,
+                                L"IsSymlinkOrJunction", path,
                                 L"expected an absolute Windows path");
     }
-    return IS_JUNCTION_ERROR;
+    return IsSymlinkOrJunctionResult::kError;
   }
 
   DWORD attrs = ::GetFileAttributesW(path);
   if (attrs == INVALID_FILE_ATTRIBUTES) {
     DWORD err = GetLastError();
+    if (err == ERROR_FILE_NOT_FOUND || err == ERROR_PATH_NOT_FOUND) {
+      return IsSymlinkOrJunctionResult::kDoesNotExist;
+    }
+
     if (error) {
       *error = MakeErrorMessage(WSTR(__FILE__), __LINE__,
-                                L"IsJunctionOrDirectorySymlink", path, err);
+                                L"IsSymlinkOrJunction", path, err);
     }
-    return IS_JUNCTION_ERROR;
+    return IsSymlinkOrJunctionResult::kError;
   } else {
-    if ((attrs & FILE_ATTRIBUTE_DIRECTORY) &&
-        (attrs & FILE_ATTRIBUTE_REPARSE_POINT)) {
-      return IS_JUNCTION_YES;
-    } else {
-      return IS_JUNCTION_NO;
-    }
+    *result = (attrs & FILE_ATTRIBUTE_REPARSE_POINT);
+    return IsSymlinkOrJunctionResult::kSuccess;
   }
 }
 
diff --git a/src/main/native/windows/file.h b/src/main/native/windows/file.h
index 87d5f0ade70c9c..0e0c41570047b4 100644
--- a/src/main/native/windows/file.h
+++ b/src/main/native/windows/file.h
@@ -48,10 +48,12 @@ std::wstring RemoveUncPrefixMaybe(const std::wstring& path);
 bool IsAbsoluteNormalizedWindowsPath(const std::wstring& p);
 
 // Keep in sync with j.c.g.devtools.build.lib.windows.WindowsFileOperations
-enum {
-  IS_JUNCTION_YES = 0,
-  IS_JUNCTION_NO = 1,
-  IS_JUNCTION_ERROR = 2,
+struct IsSymlinkOrJunctionResult {
+  enum {
+    kSuccess = 0,
+    kError = 1,
+    kDoesNotExist = 2,
+  };
 };
 
 // Keep in sync with j.c.g.devtools.build.lib.windows.WindowsFileOperations
@@ -97,16 +99,7 @@ struct ReadSymlinkOrJunctionResult {
 //
 // To read about differences between junctions and directory symlinks,
 // see http://superuser.com/a/343079. In Bazel we only ever create junctions.
-//
-// Returns:
-// - IS_JUNCTION_YES, if `path` exists and is either a directory junction or a
-//   directory symlink
-// - IS_JUNCTION_NO, if `path` exists but is neither a directory junction nor a
-//   directory symlink; also when `path` is a symlink to a directory but it was
-//   created using "mklink" instead of "mklink /d", as such symlinks don't
-//   behave the same way as directories (e.g. they can't be listed)
-// - IS_JUNCTION_ERROR, if `path` doesn't exist or some error occurred
-int IsJunctionOrDirectorySymlink(const WCHAR* path, std::wstring* error);
+int IsSymlinkOrJunction(const WCHAR* path, bool* result, wstring* error);
 
 // Computes the long version of `path` if it has any 8dot3 style components.
 // Returns the empty string upon success, or a human-readable error message upon


Test patch:
diff --git a/src/test/java/com/google/devtools/build/lib/windows/WindowsFileOperationsTest.java b/src/test/java/com/google/devtools/build/lib/windows/WindowsFileOperationsTest.java
index c5a50d32ab7895..11ceed1d74192c 100644
--- a/src/test/java/com/google/devtools/build/lib/windows/WindowsFileOperationsTest.java
+++ b/src/test/java/com/google/devtools/build/lib/windows/WindowsFileOperationsTest.java
@@ -90,32 +90,32 @@ public void testIsJunction() throws Exception {
 
     testUtil.createJunctions(junctions);
 
-    assertThat(WindowsFileOperations.isJunction(root + "\\shrtpath\\a")).isTrue();
-    assertThat(WindowsFileOperations.isJunction(root + "\\shrtpath\\b")).isTrue();
-    assertThat(WindowsFileOperations.isJunction(root + "\\shrtpath\\c")).isTrue();
-    assertThat(WindowsFileOperations.isJunction(root + "\\longlinkpath\\a")).isTrue();
-    assertThat(WindowsFileOperations.isJunction(root + "\\longlinkpath\\b")).isTrue();
-    assertThat(WindowsFileOperations.isJunction(root + "\\longlinkpath\\c")).isTrue();
-    assertThat(WindowsFileOperations.isJunction(root + "\\longli~1\\a")).isTrue();
-    assertThat(WindowsFileOperations.isJunction(root + "\\longli~1\\b")).isTrue();
-    assertThat(WindowsFileOperations.isJunction(root + "\\longli~1\\c")).isTrue();
-    assertThat(WindowsFileOperations.isJunction(root + "\\abbreviated\\a")).isTrue();
-    assertThat(WindowsFileOperations.isJunction(root + "\\abbreviated\\b")).isTrue();
-    assertThat(WindowsFileOperations.isJunction(root + "\\abbreviated\\c")).isTrue();
-    assertThat(WindowsFileOperations.isJunction(root + "\\abbrev~1\\a")).isTrue();
-    assertThat(WindowsFileOperations.isJunction(root + "\\abbrev~1\\b")).isTrue();
-    assertThat(WindowsFileOperations.isJunction(root + "\\abbrev~1\\c")).isTrue();
-    assertThat(WindowsFileOperations.isJunction(root + "\\control\\a")).isFalse();
-    assertThat(WindowsFileOperations.isJunction(root + "\\control\\b")).isFalse();
-    assertThat(WindowsFileOperations.isJunction(root + "\\control\\c")).isFalse();
-    assertThat(WindowsFileOperations.isJunction(root + "\\shrttrgt\\file1.txt")).isFalse();
-    assertThat(WindowsFileOperations.isJunction(root + "\\longtargetpath\\file2.txt")).isFalse();
-    assertThat(WindowsFileOperations.isJunction(root + "\\longta~1\\file2.txt")).isFalse();
+    assertThat(WindowsFileOperations.isSymlinkOrJunction(root + "\\shrtpath\\a")).isTrue();
+    assertThat(WindowsFileOperations.isSymlinkOrJunction(root + "\\shrtpath\\b")).isTrue();
+    assertThat(WindowsFileOperations.isSymlinkOrJunction(root + "\\shrtpath\\c")).isTrue();
+    assertThat(WindowsFileOperations.isSymlinkOrJunction(root + "\\longlinkpath\\a")).isTrue();
+    assertThat(WindowsFileOperations.isSymlinkOrJunction(root + "\\longlinkpath\\b")).isTrue();
+    assertThat(WindowsFileOperations.isSymlinkOrJunction(root + "\\longlinkpath\\c")).isTrue();
+    assertThat(WindowsFileOperations.isSymlinkOrJunction(root + "\\longli~1\\a")).isTrue();
+    assertThat(WindowsFileOperations.isSymlinkOrJunction(root + "\\longli~1\\b")).isTrue();
+    assertThat(WindowsFileOperations.isSymlinkOrJunction(root + "\\longli~1\\c")).isTrue();
+    assertThat(WindowsFileOperations.isSymlinkOrJunction(root + "\\abbreviated\\a")).isTrue();
+    assertThat(WindowsFileOperations.isSymlinkOrJunction(root + "\\abbreviated\\b")).isTrue();
+    assertThat(WindowsFileOperations.isSymlinkOrJunction(root + "\\abbreviated\\c")).isTrue();
+    assertThat(WindowsFileOperations.isSymlinkOrJunction(root + "\\abbrev~1\\a")).isTrue();
+    assertThat(WindowsFileOperations.isSymlinkOrJunction(root + "\\abbrev~1\\b")).isTrue();
+    assertThat(WindowsFileOperations.isSymlinkOrJunction(root + "\\abbrev~1\\c")).isTrue();
+    assertThat(WindowsFileOperations.isSymlinkOrJunction(root + "\\control\\a")).isFalse();
+    assertThat(WindowsFileOperations.isSymlinkOrJunction(root + "\\control\\b")).isFalse();
+    assertThat(WindowsFileOperations.isSymlinkOrJunction(root + "\\control\\c")).isFalse();
+    assertThat(WindowsFileOperations.isSymlinkOrJunction(root + "\\shrttrgt\\file1.txt")).isFalse();
+    assertThat(WindowsFileOperations.isSymlinkOrJunction(root + "\\longtargetpath\\file2.txt")).isFalse();
+    assertThat(WindowsFileOperations.isSymlinkOrJunction(root + "\\longta~1\\file2.txt")).isFalse();
     try {
-      WindowsFileOperations.isJunction(root + "\\non-existent");
+      WindowsFileOperations.isSymlinkOrJunction(root + "\\non-existent");
       fail("expected to throw");
     } catch (IOException e) {
-      assertThat(e.getMessage()).contains("nativeIsJunction");
+      assertThat(e.getMessage()).contains("path does not exist");
     }
     assertThat(Arrays.asList(new File(root + "/shrtpath/a").list())).containsExactly("file1.txt");
     assertThat(Arrays.asList(new File(root + "/shrtpath/b").list())).containsExactly("file2.txt");
@@ -141,14 +141,14 @@ public void testIsJunctionIsTrueForDanglingJunction() throws Exception {
 
     File linkPath = new File(helloPath.getParent().getParent().toFile(), "link");
     assertThat(Arrays.asList(linkPath.list())).containsExactly("hello.txt");
-    assertThat(WindowsFileOperations.isJunction(linkPath.getAbsolutePath())).isTrue();
+    assertThat(WindowsFileOperations.isSymlinkOrJunction(linkPath.getAbsolutePath())).isTrue();
 
     assertThat(helloPath.toFile().delete()).isTrue();
     assertThat(helloPath.getParent().toFile().delete()).isTrue();
     assertThat(helloPath.getParent().toFile().exists()).isFalse();
     assertThat(Arrays.asList(linkPath.getParentFile().list())).containsExactly("link");
 
-    assertThat(WindowsFileOperations.isJunction(linkPath.getAbsolutePath())).isTrue();
+    assertThat(WindowsFileOperations.isSymlinkOrJunction(linkPath.getAbsolutePath())).isTrue();
     assertThat(
             Files.exists(
                 linkPath.toPath(), WindowsFileSystem.symlinkOpts(/* followSymlinks */ false)))
@@ -167,22 +167,22 @@ public void testIsJunctionHandlesFilesystemChangesCorrectly() throws Exception {
     // Assert that a file is identified as not a junction.
     String longPath = helloFile.getAbsolutePath();
     String shortPath = new File(helloFile.getParentFile(), "hellow~1.txt").getAbsolutePath();
-    assertThat(WindowsFileOperations.isJunction(longPath)).isFalse();
-    assertThat(WindowsFileOperations.isJunction(shortPath)).isFalse();
+    assertThat(WindowsFileOperations.isSymlinkOrJunction(longPath)).isFalse();
+    assertThat(WindowsFileOperations.isSymlinkOrJunction(shortPath)).isFalse();
 
     // Assert that after deleting the file and creating a junction with the same path, it is
     // identified as a junction.
     assertThat(helloFile.delete()).isTrue();
     testUtil.createJunctions(ImmutableMap.of("target\\helloworld.txt", "target"));
-    assertThat(WindowsFileOperations.isJunction(longPath)).isTrue();
-    assertThat(WindowsFileOperations.isJunction(shortPath)).isTrue();
+    assertThat(WindowsFileOperations.isSymlinkOrJunction(longPath)).isTrue();
+    assertThat(WindowsFileOperations.isSymlinkOrJunction(shortPath)).isTrue();
 
     // Assert that after deleting the file and creating a directory with the same path, it is
     // identified as not a junction.
     assertThat(helloFile.delete()).isTrue();
     assertThat(helloFile.mkdir()).isTrue();
-    assertThat(WindowsFileOperations.isJunction(longPath)).isFalse();
-    assertThat(WindowsFileOperations.isJunction(shortPath)).isFalse();
+    assertThat(WindowsFileOperations.isSymlinkOrJunction(longPath)).isFalse();
+    assertThat(WindowsFileOperations.isSymlinkOrJunction(shortPath)).isFalse();
   }
 
   @Test
diff --git a/src/test/java/com/google/devtools/build/lib/windows/WindowsFileSystemTest.java b/src/test/java/com/google/devtools/build/lib/windows/WindowsFileSystemTest.java
index b0c9454d5121dc..7ca6e57656a18c 100644
--- a/src/test/java/com/google/devtools/build/lib/windows/WindowsFileSystemTest.java
+++ b/src/test/java/com/google/devtools/build/lib/windows/WindowsFileSystemTest.java
@@ -141,35 +141,35 @@ public void testIsJunction() throws Exception {
 
     testUtil.createJunctions(junctions);
 
-    assertThat(WindowsFileSystem.isJunction(new File(root, "shrtpath/a"))).isTrue();
-    assertThat(WindowsFileSystem.isJunction(new File(root, "shrtpath/b"))).isTrue();
-    assertThat(WindowsFileSystem.isJunction(new File(root, "shrtpath/c"))).isTrue();
-    assertThat(WindowsFileSystem.isJunction(new File(root, "longlinkpath/a"))).isTrue();
-    assertThat(WindowsFileSystem.isJunction(new File(root, "longlinkpath/b"))).isTrue();
-    assertThat(WindowsFileSystem.isJunction(new File(root, "longlinkpath/c"))).isTrue();
-    assertThat(WindowsFileSystem.isJunction(new File(root, "longli~1/a"))).isTrue();
-    assertThat(WindowsFileSystem.isJunction(new File(root, "longli~1/b"))).isTrue();
-    assertThat(WindowsFileSystem.isJunction(new File(root, "longli~1/c"))).isTrue();
-    assertThat(WindowsFileSystem.isJunction(new File(root, "abbreviated/a"))).isTrue();
-    assertThat(WindowsFileSystem.isJunction(new File(root, "abbreviated/b"))).isTrue();
-    assertThat(WindowsFileSystem.isJunction(new File(root, "abbreviated/c"))).isTrue();
-    assertThat(WindowsFileSystem.isJunction(new File(root, "abbrev~1/a"))).isTrue();
-    assertThat(WindowsFileSystem.isJunction(new File(root, "abbrev~1/b"))).isTrue();
-    assertThat(WindowsFileSystem.isJunction(new File(root, "abbrev~1/c"))).isTrue();
-    assertThat(WindowsFileSystem.isJunction(new File(root, "control/a"))).isFalse();
-    assertThat(WindowsFileSystem.isJunction(new File(root, "control/b"))).isFalse();
-    assertThat(WindowsFileSystem.isJunction(new File(root, "control/c"))).isFalse();
-    assertThat(WindowsFileSystem.isJunction(new File(root, "shrttrgt/file1.txt")))
+    assertThat(WindowsFileSystem.isSymlinkOrJunction(new File(root, "shrtpath/a"))).isTrue();
+    assertThat(WindowsFileSystem.isSymlinkOrJunction(new File(root, "shrtpath/b"))).isTrue();
+    assertThat(WindowsFileSystem.isSymlinkOrJunction(new File(root, "shrtpath/c"))).isTrue();
+    assertThat(WindowsFileSystem.isSymlinkOrJunction(new File(root, "longlinkpath/a"))).isTrue();
+    assertThat(WindowsFileSystem.isSymlinkOrJunction(new File(root, "longlinkpath/b"))).isTrue();
+    assertThat(WindowsFileSystem.isSymlinkOrJunction(new File(root, "longlinkpath/c"))).isTrue();
+    assertThat(WindowsFileSystem.isSymlinkOrJunction(new File(root, "longli~1/a"))).isTrue();
+    assertThat(WindowsFileSystem.isSymlinkOrJunction(new File(root, "longli~1/b"))).isTrue();
+    assertThat(WindowsFileSystem.isSymlinkOrJunction(new File(root, "longli~1/c"))).isTrue();
+    assertThat(WindowsFileSystem.isSymlinkOrJunction(new File(root, "abbreviated/a"))).isTrue();
+    assertThat(WindowsFileSystem.isSymlinkOrJunction(new File(root, "abbreviated/b"))).isTrue();
+    assertThat(WindowsFileSystem.isSymlinkOrJunction(new File(root, "abbreviated/c"))).isTrue();
+    assertThat(WindowsFileSystem.isSymlinkOrJunction(new File(root, "abbrev~1/a"))).isTrue();
+    assertThat(WindowsFileSystem.isSymlinkOrJunction(new File(root, "abbrev~1/b"))).isTrue();
+    assertThat(WindowsFileSystem.isSymlinkOrJunction(new File(root, "abbrev~1/c"))).isTrue();
+    assertThat(WindowsFileSystem.isSymlinkOrJunction(new File(root, "control/a"))).isFalse();
+    assertThat(WindowsFileSystem.isSymlinkOrJunction(new File(root, "control/b"))).isFalse();
+    assertThat(WindowsFileSystem.isSymlinkOrJunction(new File(root, "control/c"))).isFalse();
+    assertThat(WindowsFileSystem.isSymlinkOrJunction(new File(root, "shrttrgt/file1.txt")))
         .isFalse();
-    assertThat(WindowsFileSystem.isJunction(new File(root, "longtargetpath/file2.txt")))
+    assertThat(WindowsFileSystem.isSymlinkOrJunction(new File(root, "longtargetpath/file2.txt")))
         .isFalse();
-    assertThat(WindowsFileSystem.isJunction(new File(root, "longta~1/file2.txt")))
+    assertThat(WindowsFileSystem.isSymlinkOrJunction(new File(root, "longta~1/file2.txt")))
         .isFalse();
     try {
-      WindowsFileSystem.isJunction(new File(root, "non-existent"));
+      WindowsFileSystem.isSymlinkOrJunction(new File(root, "non-existent"));
       fail("expected failure");
     } catch (IOException e) {
-      assertThat(e.getMessage()).contains("cannot find");
+      assertThat(e.getMessage()).contains("path does not exist");
     }
 
     assertThat(Arrays.asList(new File(root + "/shrtpath/a").list())).containsExactly("file1.txt");
@@ -196,14 +196,14 @@ public void testIsJunctionIsTrueForDanglingJunction() throws Exception {
 
     File linkPath = new File(helloPath.getParent().getParent().toFile(), "link");
     assertThat(Arrays.asList(linkPath.list())).containsExactly("hello.txt");
-    assertThat(WindowsFileSystem.isJunction(linkPath)).isTrue();
+    assertThat(WindowsFileSystem.isSymlinkOrJunction(linkPath)).isTrue();
 
     assertThat(helloPath.toFile().delete()).isTrue();
     assertThat(helloPath.getParent().toFile().delete()).isTrue();
     assertThat(helloPath.getParent().toFile().exists()).isFalse();
     assertThat(Arrays.asList(linkPath.getParentFile().list())).containsExactly("link");
 
-    assertThat(WindowsFileSystem.isJunction(linkPath)).isTrue();
+    assertThat(WindowsFileSystem.isSymlinkOrJunction(linkPath)).isTrue();
     assertThat(
             Files.exists(
                 linkPath.toPath(), WindowsFileSystem.symlinkOpts(/* followSymlinks */ false)))
@@ -219,18 +219,18 @@ public void testIsJunctionHandlesFilesystemChangesCorrectly() throws Exception {
     File longPath =
         testUtil.scratchFile("target\\helloworld.txt", "hello").toAbsolutePath().toFile();
     File shortPath = new File(longPath.getParentFile(), "hellow~1.txt");
-    assertThat(WindowsFileSystem.isJunction(longPath)).isFalse();
-    assertThat(WindowsFileSystem.isJunction(shortPath)).isFalse();
+    assertThat(WindowsFileSystem.isSymlinkOrJunction(longPath)).isFalse();
+    assertThat(WindowsFileSystem.isSymlinkOrJunction(shortPath)).isFalse();
 
     assertThat(longPath.delete()).isTrue();
     testUtil.createJunctions(ImmutableMap.of("target\\helloworld.txt", "target"));
-    assertThat(WindowsFileSystem.isJunction(longPath)).isTrue();
-    assertThat(WindowsFileSystem.isJunction(shortPath)).isTrue();
+    assertThat(WindowsFileSystem.isSymlinkOrJunction(longPath)).isTrue();
+    assertThat(WindowsFileSystem.isSymlinkOrJunction(shortPath)).isTrue();
 
     assertThat(longPath.delete()).isTrue();
     assertThat(longPath.mkdir()).isTrue();
-    assertThat(WindowsFileSystem.isJunction(longPath)).isFalse();
-    assertThat(WindowsFileSystem.isJunction(shortPath)).isFalse();
+    assertThat(WindowsFileSystem.isSymlinkOrJunction(longPath)).isFalse();
+    assertThat(WindowsFileSystem.isSymlinkOrJunction(shortPath)).isFalse();
   }
 
   @Test
@@ -311,7 +311,7 @@ public void testCreateSymbolicLink() throws Exception {
     fs.createSymbolicLink(link4, fs.getPath(scratchRoot).getRelative("bar.txt").asFragment());
     // Assert that link1 and link2 are true junctions and have the right contents.
     for (Path p : ImmutableList.of(link1, link2)) {
-      assertThat(WindowsFileSystem.isJunction(new File(p.getPathString()))).isTrue();
+      assertThat(WindowsFileSystem.isSymlinkOrJunction(new File(p.getPathString()))).isTrue();
       assertThat(p.isSymbolicLink()).isTrue();
       assertThat(
               Iterables.transform(
@@ -326,7 +326,7 @@ public String apply(File input) {
     }
     // Assert that link3 and link4 are copies of files.
     for (Path p : ImmutableList.of(link3, link4)) {
-      assertThat(WindowsFileSystem.isJunction(new File(p.getPathString()))).isFalse();
+      assertThat(WindowsFileSystem.isSymlinkOrJunction(new File(p.getPathString()))).isFalse();
       assertThat(p.isSymbolicLink()).isFalse();
       assertThat(p.isFile()).isTrue();
     }
diff --git a/src/test/native/windows/file_test.cc b/src/test/native/windows/file_test.cc
index ca7604d5f45409..c4a90227cac57e 100644
--- a/src/test/native/windows/file_test.cc
+++ b/src/test/native/windows/file_test.cc
@@ -81,8 +81,10 @@ TEST_F(WindowsFileOperationsTest, TestCreateJunction) {
   wstring file1(target + L"\\foo");
   EXPECT_TRUE(blaze_util::CreateDummyFile(file1));
 
-  EXPECT_EQ(IS_JUNCTION_NO,
-            IsJunctionOrDirectorySymlink(target.c_str(), nullptr));
+  bool is_link = true;
+  EXPECT_EQ(IsSymlinkOrJunctionResult::kSuccess,
+            IsSymlinkOrJunction(target.c_str(), &is_link, nullptr));
+  EXPECT_FALSE(is_link);
   EXPECT_NE(INVALID_FILE_ATTRIBUTES, ::GetFileAttributesW(file1.c_str()));
 
   wstring name(tmp + L"\\junc_name");
@@ -99,14 +101,22 @@ TEST_F(WindowsFileOperationsTest, TestCreateJunction) {
             CreateJunctionResult::kSuccess);
 
   // Assert creation of the junctions.
-  ASSERT_EQ(IS_JUNCTION_YES,
-            IsJunctionOrDirectorySymlink((name + L"1").c_str(), nullptr));
-  ASSERT_EQ(IS_JUNCTION_YES,
-            IsJunctionOrDirectorySymlink((name + L"2").c_str(), nullptr));
-  ASSERT_EQ(IS_JUNCTION_YES,
-            IsJunctionOrDirectorySymlink((name + L"3").c_str(), nullptr));
-  ASSERT_EQ(IS_JUNCTION_YES,
-            IsJunctionOrDirectorySymlink((name + L"4").c_str(), nullptr));
+  is_link = false;
+  ASSERT_EQ(IsSymlinkOrJunctionResult::kSuccess,
+            IsSymlinkOrJunction((name + L"1").c_str(), &is_link, nullptr));
+  ASSERT_TRUE(is_link);
+  is_link = false;
+  ASSERT_EQ(IsSymlinkOrJunctionResult::kSuccess,
+            IsSymlinkOrJunction((name + L"2").c_str(), &is_link, nullptr));
+  ASSERT_TRUE(is_link);
+  is_link = false;
+  ASSERT_EQ(IsSymlinkOrJunctionResult::kSuccess,
+            IsSymlinkOrJunction((name + L"3").c_str(), &is_link, nullptr));
+  ASSERT_TRUE(is_link);
+  is_link = false;
+  ASSERT_EQ(IsSymlinkOrJunctionResult::kSuccess,
+            IsSymlinkOrJunction((name + L"4").c_str(), &is_link, nullptr));
+  ASSERT_TRUE(is_link);
 
   // Assert that the file is visible under all junctions.
   ASSERT_NE(INVALID_FILE_ATTRIBUTES,