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);
+ }
}