Update from Google.

--
MOE_MIGRATED_REVID=85702957
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
new file mode 100644
index 0000000..b349b53
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/vfs/UnionFileSystem.java
@@ -0,0 +1,419 @@
+// Copyright 2014 Google Inc. 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.vfs;
+
+import com.google.common.base.Preconditions;
+import com.google.common.collect.Lists;
+import com.google.devtools.build.lib.concurrent.ThreadSafety;
+import com.google.devtools.build.lib.util.StringTrie;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.Collection;
+import java.util.Map;
+
+import javax.annotation.Nullable;
+
+/**
+ * Presents a unified view of multiple virtual {@link FileSystem} instances, to which requests are
+ * delegated based on a {@link PathFragment} prefix mapping.
+ * If multiple prefixes apply to a given path, the *longest* (i.e. most specific) match is used.
+ * The order in which the delegates are specified does not influence the mapping.
+ *
+ * <p>Paths are preserved absolutely, contrary to how "mount" works, e.g.:
+ *    /foo/bar maps to /foo/bar on the delegate, even if it is mounted at /foo.
+ *
+ * <p>For example:
+ * "/in" maps to InFileSystem, "/" maps to OtherFileSystem.
+ * Reading from "/in/base/BUILD" through the UnionFileSystem will delegate the read operation to
+ * InFileSystem, which will read "/in/base/BUILD" relative to its root.
+ * ("mount" behavior would remap it to "/base/BUILD" on the delegate).
+ *
+ * <p>Intra-filesystem symbolic links are resolved to their ultimate targets.
+ * Cross-filesystem links are not currently supported.
+ */
+@ThreadSafety.ThreadSafe
+public class UnionFileSystem extends FileSystem {
+
+  // Prefix trie index, allowing children to easily inherit prefix mappings
+  // of their parents.
+  // This does not currently handle unicode filenames.
+  private StringTrie<FileSystem> pathDelegate;
+
+  // True iff the filesystem can be modified. If false, mutating operations
+  // will throw UnsupportedOperationExceptions.
+  private final boolean readOnly;
+
+  /**
+   * Creates a new modifiable UnionFileSystem with prefix mappings
+   * specified by a map.
+   *
+   * @param prefixMapping map of path prefixes to {@link FileSystem}s
+   */
+  public UnionFileSystem(Map<PathFragment, FileSystem> prefixMapping,
+                         FileSystem rootFileSystem) {
+    this(prefixMapping, rootFileSystem, /* readOnly */ false);
+  }
+
+  /**
+   * Creates a new modifiable or read-only UnionFileSystem with prefix mappings
+   * specified by a map.
+   *
+   * @param prefixMapping map of path prefixes to delegate {@link FileSystem}s
+   * @param rootFileSystem root for default requests; i.e. mapping of "/"
+   * @param readOnly if true, mutating operations will throw
+   */
+  public UnionFileSystem(Map<PathFragment, FileSystem> prefixMapping,
+                         FileSystem rootFileSystem, boolean readOnly) {
+    super();
+    Preconditions.checkNotNull(prefixMapping);
+    Preconditions.checkNotNull(rootFileSystem);
+    Preconditions.checkArgument(rootFileSystem != this, "Circular root filesystem.");
+    Preconditions.checkArgument(
+        !prefixMapping.containsKey(PathFragment.EMPTY_FRAGMENT),
+        "Attempted to specify an explicit root prefix mapping; " +
+        "please use the rootFileSystem argument instead.");
+
+    this.readOnly = readOnly;
+    this.pathDelegate = new StringTrie<FileSystem>();
+
+    for (Map.Entry<PathFragment, FileSystem> prefix : prefixMapping.entrySet()) {
+      FileSystem delegate = prefix.getValue();
+      PathFragment prefixPath = prefix.getKey();
+
+      // Extra slash prevents within-directory mappings, which Path can't handle.
+      String path = prefixPath.getPathString();
+      pathDelegate.put(path, delegate);
+    }
+    pathDelegate.put(PathFragment.EMPTY_FRAGMENT.getPathString(), rootFileSystem);
+  }
+
+  /**
+   * Retrieves the filesystem delegate of a path mapping.
+   * Does not follow symlinks (but you can call on a path preprocessed with
+   * {@link #resolveSymbolicLinks} to support this use case).
+   *
+   * @param path the {@link Path} to map to a filesystem
+   * @throws IllegalArgumentException if no delegate exists for the path
+   */
+  protected FileSystem getDelegate(Path path) {
+    Preconditions.checkNotNull(path);
+
+    String pathString = path.getPathString();
+    FileSystem immediateDelegate = pathDelegate.get(pathString);
+
+    // Should never actually happen if the root delegate is present.
+    Preconditions.checkArgument(immediateDelegate != null, "No delegate filesystem exists for %s",
+        pathString);
+    return immediateDelegate;
+  }
+
+  // Associates the path with the root of the given delegate filesystem.
+  // Necessary to avoid null pointer problems inside of the delegates.
+  protected Path adjustPath(Path path, FileSystem delegate) {
+    return delegate.getPath(path.asFragment());
+  }
+
+  /**
+   * Follow a symbolic link once using the appropriate delegate filesystem, also
+   * resolving parent directory symlinks.
+   *
+   * @param path {@link Path} to the symbolic link
+   */
+  @Override
+  protected PathFragment readSymbolicLink(Path path) throws IOException {
+    Preconditions.checkNotNull(path);
+    FileSystem delegate = getDelegate(path);
+    return delegate.readSymbolicLink(adjustPath(path, delegate));
+  }
+
+  @Override
+  protected PathFragment resolveOneLink(Path path) throws IOException {
+    Preconditions.checkNotNull(path);
+    FileSystem delegate = getDelegate(path);
+    return delegate.resolveOneLink(adjustPath(path, delegate));
+  }
+
+  private void checkModifiable() {
+    if (!supportsModifications()) {
+      throw new UnsupportedOperationException(
+          "Modifications to this " + getClass().getSimpleName() + " are disabled.");
+    }
+  }
+
+  @Override
+  public boolean supportsModifications() {
+    return !readOnly;
+  }
+
+  @Override
+  public boolean supportsSymbolicLinks() {
+    return true;
+  }
+
+  @Override
+  public String getFileSystemType(Path path) {
+    FileSystem delegate = getDelegate(path);
+    return delegate.getFileSystemType(path);
+  }
+
+  @Override
+  protected byte[] getMD5Digest(Path path) throws IOException {
+    FileSystem delegate = getDelegate(path);
+    return delegate.getMD5Digest(adjustPath(path, delegate));
+  }
+
+  @Override
+  protected boolean createDirectory(Path path) throws IOException {
+    checkModifiable();
+    // When creating the exact directory that is mapped,
+    // create it on both the parent's delegate and the path's delegate.
+    // This is necessary both for the parent to see the directory and for the
+    // delegate to use it.
+    // This is present to address this problematic case:
+    //   / -> RootFs
+    //   /foo -> FooFs
+    //   mkdir /foo
+    //   ls / ("foo" would be missing if not created on the parent)
+    //   ls /foo (would fail if foo weren't also present on the child)
+    FileSystem delegate = getDelegate(path);
+    Path parent = path.getParentDirectory();
+    if (parent != null) {
+      FileSystem parentDelegate = getDelegate(parent);
+      if (parentDelegate != delegate) {
+        // There's a possibility it already exists on the parent, so don't die
+        // if the directory can't be created there.
+        parentDelegate.createDirectory(adjustPath(path, parentDelegate));
+      }
+    }
+    return delegate.createDirectory(adjustPath(path, delegate));
+  }
+
+  @Override
+  protected long getFileSize(Path path, boolean followSymlinks) throws IOException {
+    FileSystem delegate = getDelegate(path);
+    return delegate.getFileSize(adjustPath(path, delegate), followSymlinks);
+  }
+
+  @Override
+  protected boolean delete(Path path) throws IOException {
+    checkModifiable();
+    FileSystem delegate = getDelegate(path);
+    return delegate.delete(adjustPath(path, delegate));
+  }
+
+  @Override
+  protected long getLastModifiedTime(Path path, boolean followSymlinks) throws IOException {
+    FileSystem delegate = getDelegate(path);
+    return delegate.getLastModifiedTime(adjustPath(path, delegate), followSymlinks);
+  }
+
+  @Override
+  protected void setLastModifiedTime(Path path, long newTime) throws IOException {
+    checkModifiable();
+    FileSystem delegate = getDelegate(path);
+    delegate.setLastModifiedTime(adjustPath(path, delegate), newTime);
+  }
+
+  @Override
+  protected boolean isSymbolicLink(Path path) {
+    FileSystem delegate = getDelegate(path);
+    path = adjustPath(path, delegate);
+    return delegate.isSymbolicLink(path);
+  }
+
+  @Override
+  protected boolean isDirectory(Path path, boolean followSymlinks) {
+    FileSystem delegate = getDelegate(path);
+    return delegate.isDirectory(adjustPath(path, delegate), followSymlinks);
+  }
+
+  @Override
+  protected boolean isFile(Path path, boolean followSymlinks) {
+    FileSystem delegate = getDelegate(path);
+    return delegate.isFile(adjustPath(path, delegate), followSymlinks);
+  }
+
+  @Override
+  protected void createSymbolicLink(Path linkPath, PathFragment targetFragment) throws IOException {
+    checkModifiable();
+    if (!supportsSymbolicLinks()) {
+      throw new UnsupportedOperationException(
+          "Attempted to create a symlink, but symlink support is disabled.");
+    }
+
+    FileSystem delegate = getDelegate(linkPath);
+    delegate.createSymbolicLink(adjustPath(linkPath, delegate), targetFragment);
+  }
+
+  @Override
+  protected boolean exists(Path path, boolean followSymlinks) {
+    FileSystem delegate = getDelegate(path);
+    return delegate.exists(adjustPath(path, delegate), followSymlinks);
+  }
+
+  @Override
+  protected FileStatus stat(final Path path, final boolean followSymlinks) throws IOException {
+    FileSystem delegate = getDelegate(path);
+    return delegate.stat(adjustPath(path, delegate), followSymlinks);
+  }
+
+  // Needs to be overridden for the delegation logic, because the
+  // UnixFileSystem implements statNullable and stat as separate codepaths.
+  // More generally, we wish to delegate all filesystem operations.
+  @Override
+  protected FileStatus statNullable(Path path, boolean followSymlinks) {
+    FileSystem delegate = getDelegate(path);
+    return delegate.statNullable(adjustPath(path, delegate), followSymlinks);
+  }
+
+  @Override
+  @Nullable
+  protected FileStatus statIfFound(Path path, boolean followSymlinks) throws IOException {
+    FileSystem delegate = getDelegate(path);
+    return delegate.statIfFound(adjustPath(path, delegate), followSymlinks);
+  }
+
+  /**
+   * Retrieves the directory entries for the specified path under the assumption
+   * that {@code resolvedPath} is the resolved path of {@code path} in one of the
+   * underlying file systems.
+   *
+   * @param path the {@link Path} whose children are to be retrieved
+   */
+  @Override
+  protected Collection<Path> getDirectoryEntries(Path path) throws IOException {
+    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()));
+    }
+    return result;
+  }
+
+  // No need for the more complex logic of getDirectoryEntries; it calls it implicitly.
+  @Override
+  protected Collection<Dirent> readdir(Path path, boolean followSymlinks) throws IOException {
+    FileSystem delegate = getDelegate(path);
+    return delegate.readdir(adjustPath(path, delegate), followSymlinks);
+  }
+
+  @Override
+  protected boolean isReadable(Path path) throws IOException {
+    FileSystem delegate = getDelegate(path);
+    return delegate.isReadable(adjustPath(path, delegate));
+  }
+
+  @Override
+  protected void setReadable(Path path, boolean readable) throws IOException {
+    checkModifiable();
+    FileSystem delegate = getDelegate(path);
+    delegate.setReadable(adjustPath(path, delegate), readable);
+  }
+
+  @Override
+  protected boolean isWritable(Path path) throws IOException {
+    if (!supportsModifications()) {
+      return false;
+    }
+    FileSystem delegate = getDelegate(path);
+    return delegate.isWritable(adjustPath(path, delegate));
+  }
+
+  @Override
+  protected void setWritable(Path path, boolean writable) throws IOException {
+    checkModifiable();
+    FileSystem delegate = getDelegate(path);
+    delegate.setWritable(adjustPath(path, delegate), writable);
+  }
+
+  @Override
+  protected boolean isExecutable(Path path) throws IOException {
+    FileSystem delegate = getDelegate(path);
+    return delegate.isExecutable(adjustPath(path, delegate));
+  }
+
+  @Override
+  protected void setExecutable(Path path, boolean executable) throws IOException {
+    checkModifiable();
+    FileSystem delegate = getDelegate(path);
+    delegate.setExecutable(adjustPath(path, delegate), executable);
+  }
+
+  @Override
+  protected String getFastDigestFunctionType(Path path) {
+    FileSystem delegate = getDelegate(path);
+    return delegate.getFastDigestFunctionType(adjustPath(path, delegate));
+  }
+
+  @Override
+  protected byte[] getFastDigest(Path path) throws IOException {
+    FileSystem delegate = getDelegate(path);
+    return delegate.getFastDigest(adjustPath(path, delegate));
+  }
+
+  @Override
+  protected byte[] getxattr(Path path, String name, boolean followSymlinks) throws IOException {
+    FileSystem delegate = getDelegate(path);
+    return delegate.getxattr(adjustPath(path, delegate), name, followSymlinks);
+  }
+
+  @Override
+  protected InputStream getInputStream(Path path) throws IOException {
+    FileSystem delegate = getDelegate(path);
+    return delegate.getInputStream(adjustPath(path, delegate));
+  }
+
+  @Override
+  protected OutputStream getOutputStream(Path path, boolean append) throws IOException {
+    checkModifiable();
+    FileSystem delegate = getDelegate(path);
+    return delegate.getOutputStream(adjustPath(path, delegate), append);
+  }
+
+  @Override
+  protected void renameTo(Path sourcePath, Path targetPath) throws IOException {
+    checkModifiable();
+    FileSystem sourceDelegate = getDelegate(sourcePath);
+    if (!sourceDelegate.supportsModifications()) {
+      throw new UnsupportedOperationException(
+          "The filesystem for the source path "
+          + sourcePath.getPathString() + " does not support modifications.");
+    }
+    sourcePath = adjustPath(sourcePath, sourceDelegate);
+
+    FileSystem targetDelegate = getDelegate(targetPath);
+    if (!targetDelegate.supportsModifications()) {
+      throw new UnsupportedOperationException(
+          "The filesystem for the target path "
+          + targetPath.getPathString() + " does not support modifications.");
+    }
+    targetPath = adjustPath(targetPath, targetDelegate);
+
+    if (sourceDelegate == targetDelegate) {
+      // Easy, same filesystem.
+      sourceDelegate.renameTo(sourcePath, targetPath);
+      return;
+    } else {
+      // Copy across filesystems, then delete.
+      // copyFile throws on failure, so delete will never be reached if it fails.
+      FileSystemUtils.copyFile(sourcePath, targetPath);
+      sourceDelegate.delete(sourcePath);
+    }
+  }
+}