Windows: recognize symlinks, not just junctions

In this PR:

- The isJunction JNI method now recognizes
  symlinks, not just junctions. The method now
  reports errors via return codes.

- WindowsFileSystem.fileIsSymbolicLink and
  WindowsFileSystem.isSymlinkOrJunction now
  recognize symlinks.

See https://github.com/bazelbuild/bazel/issues/7907

Closes #7931.

PiperOrigin-RevId: 241927717
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 c5a50d3..9c04f8b 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,33 @@
 
     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 +142,14 @@
 
     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 +168,22 @@
     // 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 b0c9454..7ca6e57 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 @@
 
     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 @@
 
     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 @@
     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 @@
     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 @@
     }
     // 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();
     }