Fixed the issue that hard links are handled improperly when bazel decompresses tarballs. 
Issue link: https://github.com/bazelbuild/bazel/issues/574

--
MOS_MIGRATED_REVID=132434278
diff --git a/src/main/java/com/google/devtools/build/lib/vfs/FileSystem.java b/src/main/java/com/google/devtools/build/lib/vfs/FileSystem.java
index dd067d7..3e3b94d 100644
--- a/src/main/java/com/google/devtools/build/lib/vfs/FileSystem.java
+++ b/src/main/java/com/google/devtools/build/lib/vfs/FileSystem.java
@@ -22,12 +22,12 @@
 import com.google.common.io.CharStreams;
 import com.google.devtools.build.lib.concurrent.ThreadSafety.ThreadSafe;
 import com.google.devtools.build.lib.vfs.Dirent.Type;
-
 import java.io.FileNotFoundException;
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.InputStreamReader;
 import java.io.OutputStream;
+import java.nio.file.FileAlreadyExistsException;
 import java.util.Collection;
 import java.util.List;
 
@@ -128,6 +128,21 @@
    */
   public abstract boolean supportsSymbolicLinksNatively();
 
+  /**
+   * Returns whether or not the FileSystem supports hard links.
+   *
+   * <p>Returns true if FileSystem supports the following:
+   *
+   * <ul>
+   * <li>{@link #createFSDependentHardLink(Path, Path)}
+   * </ul>
+   *
+   * The above calls may result in an {@link UnsupportedOperationException} on a FileSystem where
+   * this method returns {@code false}. The implementation can try to emulate these calls at its own
+   * discretion.
+   */
+  protected abstract boolean supportsHardLinksNatively();
+
   /***
    * Returns true if file path is case-sensitive on this file system. Default is true.
    */
@@ -665,4 +680,41 @@
    * See {@link Path#renameTo} for specification.
    */
   protected abstract void renameTo(Path sourcePath, Path targetPath) throws IOException;
+
+
+  /**
+   * Create a new hard link file at "linkPath" for file at "originalPath".
+   *
+   * @param linkPath The path of the new link file to be created
+   * @param originalPath The path of the original file
+   * @throws IOException if the original file does not exist or the link file already exists
+   */
+  protected void createHardLink(Path linkPath, Path originalPath) throws IOException {
+
+    if (!originalPath.exists()) {
+      throw new FileNotFoundException(
+          "File \""
+              + originalPath.getBaseName()
+              + "\" linked from \""
+              + linkPath.getBaseName()
+              + "\" does not exist");
+    }
+
+    if (linkPath.exists()) {
+      throw new FileAlreadyExistsException(
+          "New link file \"" + linkPath.getBaseName() + "\" already exists");
+    }
+
+    createFSDependentHardLink(linkPath, originalPath);
+  }
+
+  /**
+   * Create a new hard link file at "linkPath" for file at "originalPath".
+   *
+   * @param linkPath The path of the new link file to be created
+   * @param originalPath The path of the original file
+   * @throws IOException if there was an I/O error
+   */
+  protected abstract void createFSDependentHardLink(Path linkPath, Path originalPath)
+      throws IOException;
 }
diff --git a/src/main/java/com/google/devtools/build/lib/vfs/FileSystemUtils.java b/src/main/java/com/google/devtools/build/lib/vfs/FileSystemUtils.java
index cf12b50..ee1af6c 100644
--- a/src/main/java/com/google/devtools/build/lib/vfs/FileSystemUtils.java
+++ b/src/main/java/com/google/devtools/build/lib/vfs/FileSystemUtils.java
@@ -932,4 +932,31 @@
     }
     return false;
   }
+
+
+  /**
+   * Create a new hard link file at "linkPath" for file at "originalPath". If "originalPath" is a
+   * directory, then for each entry, create link under "linkPath" recursively.
+   *
+   * @param linkPath The path of the new link file to be created
+   * @param originalPath The path of the original file
+   * @throws IOException if there was an error executing {@link Path#createHardLink}
+   */
+  public static void createHardLink(Path linkPath, Path originalPath) throws IOException {
+
+    // Regular file
+    if (originalPath.isFile()) {
+      Path parentDir = linkPath.getParentDirectory();
+      if (!parentDir.exists()) {
+        FileSystemUtils.createDirectoryAndParents(parentDir);
+      }
+      originalPath.createHardLink(linkPath);
+      // Directory
+    } else if (originalPath.isDirectory()) {
+      for (Path originalSubpath : originalPath.getDirectoryEntries()) {
+        Path linkSubpath = linkPath.getRelative(originalSubpath.relativeTo(originalPath));
+        createHardLink(linkSubpath, originalSubpath);
+      }
+    }
+  }
 }
diff --git a/src/main/java/com/google/devtools/build/lib/vfs/JavaIoFileSystem.java b/src/main/java/com/google/devtools/build/lib/vfs/JavaIoFileSystem.java
index dead488..7cc10aa 100644
--- a/src/main/java/com/google/devtools/build/lib/vfs/JavaIoFileSystem.java
+++ b/src/main/java/com/google/devtools/build/lib/vfs/JavaIoFileSystem.java
@@ -191,6 +191,11 @@
   }
 
   @Override
+  public boolean supportsHardLinksNatively() {
+    return true;
+  }
+
+  @Override
   public boolean isFilePathCaseSensitive() {
     return true;
   }
@@ -468,4 +473,12 @@
       throw new IllegalStateException(e);
     }
   }
+
+  @Override
+  protected void createFSDependentHardLink(Path linkPath, Path originalPath)
+      throws IOException {
+    Files.createLink(
+        java.nio.file.Paths.get(linkPath.toString()),
+        java.nio.file.Paths.get(originalPath.toString()));
+  }
 }
diff --git a/src/main/java/com/google/devtools/build/lib/vfs/Path.java b/src/main/java/com/google/devtools/build/lib/vfs/Path.java
index 532a5f0..657bb03 100644
--- a/src/main/java/com/google/devtools/build/lib/vfs/Path.java
+++ b/src/main/java/com/google/devtools/build/lib/vfs/Path.java
@@ -826,6 +826,16 @@
   }
 
   /**
+   * Create a hard link for the current path.
+   *
+   * @param link the path of the new link
+   * @throws IOException if there was an error executing {@link FileSystem#createHardLink}
+   */
+  public void createHardLink(Path link) throws IOException {
+    fileSystem.createHardLink(link, this);
+  }
+
+  /**
    * Returns the canonical path for this path, by repeatedly replacing symbolic
    * links with their referents. Analogous to realpath(3).
    *
@@ -1135,7 +1145,8 @@
     // requires us to always go up to the top-level directory and copy all segments into a new
     // string array.
     // This was previously showing up as a hotspot in a profile of globbing a large directory.
-    Path a = this, b = o;
+    Path a = this;
+    Path b = o;
     int maxDepth = Math.min(a.depth, b.depth);
     while (a.depth > maxDepth) {
       a = a.getParentDirectory();
@@ -1148,7 +1159,8 @@
       // If a is the same as this, this.depth must be less than o.depth.
       return equals(a) ? -1 : 1;
     }
-    Path previousa, previousb;
+    Path previousa;
+    Path previousb;
     do {
       previousa = a;
       previousb = b;
diff --git a/src/main/java/com/google/devtools/build/lib/vfs/ReadonlyFileSystem.java b/src/main/java/com/google/devtools/build/lib/vfs/ReadonlyFileSystem.java
index cbc2753..5f58eb5 100644
--- a/src/main/java/com/google/devtools/build/lib/vfs/ReadonlyFileSystem.java
+++ b/src/main/java/com/google/devtools/build/lib/vfs/ReadonlyFileSystem.java
@@ -76,6 +76,11 @@
   }
 
   @Override
+  public boolean supportsHardLinksNatively() {
+    return false;
+  }
+
+  @Override
   public boolean isFilePathCaseSensitive() {
     return true;
   }
@@ -105,4 +110,9 @@
     throw modificationException();
   }
 
+  @Override
+  protected void createFSDependentHardLink(Path linkPath, Path originalPath)
+      throws IOException {
+    throw modificationException();
+  }
 }
diff --git a/src/main/java/com/google/devtools/build/lib/vfs/ReadonlyFileSystemWithCustomStat.java b/src/main/java/com/google/devtools/build/lib/vfs/ReadonlyFileSystemWithCustomStat.java
index a3950b0..f72019b 100644
--- a/src/main/java/com/google/devtools/build/lib/vfs/ReadonlyFileSystemWithCustomStat.java
+++ b/src/main/java/com/google/devtools/build/lib/vfs/ReadonlyFileSystemWithCustomStat.java
@@ -61,6 +61,11 @@
   }
 
   @Override
+  public boolean supportsHardLinksNatively() {
+    return false;
+  }
+
+  @Override
   public boolean isFilePathCaseSensitive() {
     return true;
   }
@@ -76,6 +81,12 @@
   }
 
   @Override
+  protected void createFSDependentHardLink(Path linkPath, Path originalPath)
+      throws IOException {
+    throw modificationException();
+  }
+
+  @Override
   protected void renameTo(Path sourcePath, Path targetPath) throws IOException {
     throw modificationException();
   }
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 cf67d1e..a0a0955 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,6 +173,11 @@
   }
 
   @Override
+  public boolean supportsHardLinksNatively() {
+    return true;
+  }
+
+  @Override
   public boolean isFilePathCaseSensitive() {
     return this.isCaseSensitive;
   }
@@ -435,4 +440,20 @@
       sourceDelegate.delete(sourcePath);
     }
   }
+
+  @Override
+  protected void createFSDependentHardLink(Path linkPath, Path originalPath)
+      throws IOException {
+    checkModifiable();
+
+    FileSystem originalDelegate = getDelegate(originalPath);
+    FileSystem linkDelegate = getDelegate(linkPath);
+
+    if (!originalDelegate.equals(linkDelegate) || !linkDelegate.supportsHardLinksNatively()) {
+      throw new UnsupportedOperationException(
+          "Attempted to create a hard link, but hard link support is disabled.");
+    }
+    linkDelegate.createFSDependentHardLink(
+        adjustPath(linkPath, linkDelegate), adjustPath(originalPath, originalDelegate));
+  }
 }
diff --git a/src/main/java/com/google/devtools/build/lib/vfs/UnixFileSystem.java b/src/main/java/com/google/devtools/build/lib/vfs/UnixFileSystem.java
index ce7ee93..53db44c 100644
--- a/src/main/java/com/google/devtools/build/lib/vfs/UnixFileSystem.java
+++ b/src/main/java/com/google/devtools/build/lib/vfs/UnixFileSystem.java
@@ -289,6 +289,11 @@
   }
 
   @Override
+  public boolean supportsHardLinksNatively() {
+    return true;
+  }
+
+  @Override
   public boolean isFilePathCaseSensitive() {
     return true;
   }
@@ -403,4 +408,10 @@
       profiler.logSimpleTask(startTime, ProfilerTask.VFS_MD5, name);
     }
   }
+
+  @Override
+  protected void createFSDependentHardLink(Path linkPath, Path originalPath)
+      throws IOException {
+    NativePosixFiles.link(originalPath.toString(), linkPath.toString());
+  }
 }
diff --git a/src/main/java/com/google/devtools/build/lib/vfs/inmemoryfs/InMemoryFileSystem.java b/src/main/java/com/google/devtools/build/lib/vfs/inmemoryfs/InMemoryFileSystem.java
index dd974e5..e763ebe 100644
--- a/src/main/java/com/google/devtools/build/lib/vfs/inmemoryfs/InMemoryFileSystem.java
+++ b/src/main/java/com/google/devtools/build/lib/vfs/inmemoryfs/InMemoryFileSystem.java
@@ -655,6 +655,11 @@
   }
 
   @Override
+  public boolean supportsHardLinksNatively() {
+    return true;
+  }
+
+  @Override
   public boolean isFilePathCaseSensitive() {
     return true;
   }
@@ -931,4 +936,33 @@
       throw Error.EACCES.exception(targetPath);
     }
   }
+
+  @Override
+  protected void createFSDependentHardLink(Path linkPath, Path originalPath)
+      throws IOException {
+
+    // Same check used when creating a symbolic link
+    if (originalPath.equals(rootPath)) {
+      throw Error.EACCES.exception(originalPath);
+    }
+
+    InMemoryDirectoryInfo linkParent;
+    synchronized (this) {
+      linkParent = getDirectory(linkPath.getParentDirectory());
+      // Same check used when creating a symbolic link
+      if (!linkParent.outOfScope()) {
+        if (linkParent.getChild(linkPath.getBaseName()) != null) {
+          throw Error.EEXIST.exception(linkPath);
+        }
+        insert(
+            linkParent,
+            linkPath.getBaseName(),
+            getDirectory(originalPath.getParentDirectory()).getChild(originalPath.getBaseName()),
+            linkPath);
+        return;
+      }
+    }
+    // If we get here, we're out of scope.
+    getDelegatedPath(linkParent.getEscapingPath(), originalPath).createHardLink(linkPath);
+  }
 }