Make UnionFileSystem eagerly resolve symlinks.

This allows symlinks between multiple different file systems. InMemoryFileSystem uses this in some tests. This CL will allow deletion of ScopeEscapableFileSystem, one of two file systems that partially duplicate what UnionFileSystem does.

PiperOrigin-RevId: 172823391
diff --git a/src/main/java/com/google/devtools/build/lib/vfs/UnionFileSystem.java b/src/main/java/com/google/devtools/build/lib/vfs/UnionFileSystem.java
index 4a0148e..cab13a1 100644
--- a/src/main/java/com/google/devtools/build/lib/vfs/UnionFileSystem.java
+++ b/src/main/java/com/google/devtools/build/lib/vfs/UnionFileSystem.java
@@ -173,12 +173,18 @@
 
   @Override
   public String getFileSystemType(Path path) {
+    try {
+      path = internalResolveSymlink(path);
+    } catch (IOException e) {
+      return "unknown";
+    }
     FileSystem delegate = getDelegate(path);
     return delegate.getFileSystemType(path);
   }
 
   @Override
   protected byte[] getDigest(Path path, HashFunction hashFunction) throws IOException {
+    path = internalResolveSymlink(path);
     FileSystem delegate = getDelegate(path);
     return delegate.getDigest(adjustPath(path, delegate), hashFunction);
   }
@@ -199,6 +205,7 @@
     FileSystem delegate = getDelegate(path);
     Path parent = path.getParentDirectory();
     if (parent != null) {
+      parent = internalResolveSymlink(parent);
       FileSystem parentDelegate = getDelegate(parent);
       if (parentDelegate != delegate) {
         // There's a possibility it already exists on the parent, so don't die
@@ -211,8 +218,9 @@
 
   @Override
   protected long getFileSize(Path path, boolean followSymlinks) throws IOException {
+    path = followSymlinks ? internalResolveSymlink(path) : path;
     FileSystem delegate = getDelegate(path);
-    return delegate.getFileSize(adjustPath(path, delegate), followSymlinks);
+    return delegate.getFileSize(adjustPath(path, delegate), false);
   }
 
   @Override
@@ -224,12 +232,14 @@
 
   @Override
   protected long getLastModifiedTime(Path path, boolean followSymlinks) throws IOException {
+    path = followSymlinks ? internalResolveSymlink(path) : path;
     FileSystem delegate = getDelegate(path);
-    return delegate.getLastModifiedTime(adjustPath(path, delegate), followSymlinks);
+    return delegate.getLastModifiedTime(adjustPath(path, delegate), false);
   }
 
   @Override
   protected void setLastModifiedTime(Path path, long newTime) throws IOException {
+    path = internalResolveSymlink(path);
     checkModifiable(path);
     FileSystem delegate = getDelegate(path);
     delegate.setLastModifiedTime(adjustPath(path, delegate), newTime);
@@ -244,20 +254,35 @@
 
   @Override
   protected boolean isDirectory(Path path, boolean followSymlinks) {
+    try {
+      path = followSymlinks ? internalResolveSymlink(path) : path;
+    } catch (IOException e) {
+      return false;
+    }
     FileSystem delegate = getDelegate(path);
-    return delegate.isDirectory(adjustPath(path, delegate), followSymlinks);
+    return delegate.isDirectory(adjustPath(path, delegate), false);
   }
 
   @Override
   protected boolean isFile(Path path, boolean followSymlinks) {
+    try {
+      path = followSymlinks ? internalResolveSymlink(path) : path;
+    } catch (IOException e) {
+      return false;
+    }
     FileSystem delegate = getDelegate(path);
-    return delegate.isFile(adjustPath(path, delegate), followSymlinks);
+    return delegate.isFile(adjustPath(path, delegate), false);
   }
 
   @Override
   protected boolean isSpecialFile(Path path, boolean followSymlinks) {
+    try {
+      path = followSymlinks ? internalResolveSymlink(path) : path;
+    } catch (IOException e) {
+      return false;
+    }
     FileSystem delegate = getDelegate(path);
-    return delegate.isSpecialFile(adjustPath(path, delegate), followSymlinks);
+    return delegate.isSpecialFile(adjustPath(path, delegate), false);
   }
 
   @Override
@@ -274,14 +299,20 @@
 
   @Override
   protected boolean exists(Path path, boolean followSymlinks) {
+    try {
+      path = followSymlinks ? internalResolveSymlink(path) : path;
+    } catch (IOException e) {
+      return false;
+    }
     FileSystem delegate = getDelegate(path);
-    return delegate.exists(adjustPath(path, delegate), followSymlinks);
+    return delegate.exists(adjustPath(path, delegate), false);
   }
 
   @Override
-  protected FileStatus stat(final Path path, final boolean followSymlinks) throws IOException {
+  protected FileStatus stat(Path path, boolean followSymlinks) throws IOException {
+    path = followSymlinks ? internalResolveSymlink(path) : path;
     FileSystem delegate = getDelegate(path);
-    return delegate.stat(adjustPath(path, delegate), followSymlinks);
+    return delegate.stat(adjustPath(path, delegate), false);
   }
 
   // Needs to be overridden for the delegation logic, because the
@@ -289,15 +320,21 @@
   // More generally, we wish to delegate all filesystem operations.
   @Override
   protected FileStatus statNullable(Path path, boolean followSymlinks) {
+    try {
+      path = followSymlinks ? internalResolveSymlink(path) : path;
+    } catch (IOException e) {
+      return null;
+    }
     FileSystem delegate = getDelegate(path);
-    return delegate.statNullable(adjustPath(path, delegate), followSymlinks);
+    return delegate.statNullable(adjustPath(path, delegate), false);
   }
 
   @Override
   @Nullable
   protected FileStatus statIfFound(Path path, boolean followSymlinks) throws IOException {
+    path = followSymlinks ? internalResolveSymlink(path) : path;
     FileSystem delegate = getDelegate(path);
-    return delegate.statIfFound(adjustPath(path, delegate), followSymlinks);
+    return delegate.statIfFound(adjustPath(path, delegate), false);
   }
 
   /**
@@ -308,12 +345,14 @@
    */
   @Override
   protected Collection<Path> getDirectoryEntries(Path path) throws IOException {
+    Path origPath = path;
+    path = internalResolveSymlink(path);
     FileSystem delegate = getDelegate(path);
     Path resolvedPath = adjustPath(path, delegate);
     Collection<Path> entries = resolvedPath.getDirectoryEntries();
     Collection<Path> result = Lists.newArrayListWithCapacity(entries.size());
     for (Path entry : entries) {
-      result.add(path.getChild(entry.getBaseName()));
+      result.add(origPath.getChild(entry.getBaseName()));
     }
     return result;
   }
@@ -321,18 +360,21 @@
   // No need for the more complex logic of getDirectoryEntries; it calls it implicitly.
   @Override
   protected Collection<Dirent> readdir(Path path, boolean followSymlinks) throws IOException {
+    path = followSymlinks ? internalResolveSymlink(path) : path;
     FileSystem delegate = getDelegate(path);
-    return delegate.readdir(adjustPath(path, delegate), followSymlinks);
+    return delegate.readdir(adjustPath(path, delegate), false);
   }
 
   @Override
   protected boolean isReadable(Path path) throws IOException {
+    path = internalResolveSymlink(path);
     FileSystem delegate = getDelegate(path);
     return delegate.isReadable(adjustPath(path, delegate));
   }
 
   @Override
   protected void setReadable(Path path, boolean readable) throws IOException {
+    path = internalResolveSymlink(path);
     checkModifiable(path);
     FileSystem delegate = getDelegate(path);
     delegate.setReadable(adjustPath(path, delegate), readable);
@@ -343,6 +385,7 @@
     if (!supportsModifications(path)) {
       return false;
     }
+    path = internalResolveSymlink(path);
     FileSystem delegate = getDelegate(path);
     return delegate.isWritable(adjustPath(path, delegate));
   }
@@ -350,18 +393,21 @@
   @Override
   protected void setWritable(Path path, boolean writable) throws IOException {
     checkModifiable(path);
+    path = internalResolveSymlink(path);
     FileSystem delegate = getDelegate(path);
     delegate.setWritable(adjustPath(path, delegate), writable);
   }
 
   @Override
   protected boolean isExecutable(Path path) throws IOException {
+    path = internalResolveSymlink(path);
     FileSystem delegate = getDelegate(path);
     return delegate.isExecutable(adjustPath(path, delegate));
   }
 
   @Override
   protected void setExecutable(Path path, boolean executable) throws IOException {
+    path = internalResolveSymlink(path);
     checkModifiable(path);
     FileSystem delegate = getDelegate(path);
     delegate.setExecutable(adjustPath(path, delegate), executable);
@@ -369,24 +415,28 @@
 
   @Override
   protected byte[] getFastDigest(Path path, HashFunction hashFunction) throws IOException {
+    path = internalResolveSymlink(path);
     FileSystem delegate = getDelegate(path);
     return delegate.getFastDigest(adjustPath(path, delegate), hashFunction);
   }
 
   @Override
   protected byte[] getxattr(Path path, String name) throws IOException {
+    path = internalResolveSymlink(path);
     FileSystem delegate = getDelegate(path);
     return delegate.getxattr(adjustPath(path, delegate), name);
   }
 
   @Override
   protected InputStream getInputStream(Path path) throws IOException {
+    path = internalResolveSymlink(path);
     FileSystem delegate = getDelegate(path);
     return delegate.getInputStream(adjustPath(path, delegate));
   }
 
   @Override
   protected OutputStream getOutputStream(Path path, boolean append) throws IOException {
+    path = internalResolveSymlink(path);
     checkModifiable(path);
     FileSystem delegate = getDelegate(path);
     return delegate.getOutputStream(adjustPath(path, delegate), append);
@@ -394,6 +444,7 @@
 
   @Override
   protected void renameTo(Path sourcePath, Path targetPath) throws IOException {
+    sourcePath = internalResolveSymlink(sourcePath);
     FileSystem sourceDelegate = getDelegate(sourcePath);
     if (!sourceDelegate.supportsModifications(sourcePath)) {
       throw new UnsupportedOperationException(
@@ -428,6 +479,7 @@
   protected void createFSDependentHardLink(Path linkPath, Path originalPath) throws IOException {
     checkModifiable(linkPath);
 
+    originalPath = internalResolveSymlink(originalPath);
     FileSystem originalDelegate = getDelegate(originalPath);
     FileSystem linkDelegate = getDelegate(linkPath);
 
@@ -439,4 +491,12 @@
     linkDelegate.createFSDependentHardLink(
         adjustPath(linkPath, linkDelegate), adjustPath(originalPath, originalDelegate));
   }
+
+  private Path internalResolveSymlink(Path path) throws IOException {
+    while (isSymbolicLink(path)) {
+      PathFragment pathFragment = resolveOneLink(path);
+      path = path.getRelative(pathFragment);
+    }
+    return path;
+  }
 }