Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[7.1.0] Follow directory symlink in RemoteActionFileSystem#getDirectoryEntries(). #21088

Merged
merged 1 commit into from
Jan 27, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
import com.google.common.annotations.VisibleForTesting;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSortedSet;
import com.google.common.collect.Iterables;
import com.google.devtools.build.lib.actions.ActionExecutionMetadata;
import com.google.devtools.build.lib.actions.ActionInput;
Expand Down Expand Up @@ -63,6 +64,7 @@
import java.util.HashMap;
import java.util.HashSet;
import java.util.Set;
import java.util.function.Function;
import javax.annotation.Nullable;

/**
Expand Down Expand Up @@ -767,56 +769,24 @@ public boolean createDirectory(PathFragment path) throws IOException {
}

@Override
protected ImmutableList<String> getDirectoryEntries(PathFragment path) throws IOException {
HashSet<String> entries = new HashSet<>();

boolean found = false;

if (path.startsWith(execRoot)) {
var execPath = path.relativeTo(execRoot);
Collection<Dirent> treeEntries = inputTreeArtifactDirectoryCache.get(execPath);
if (treeEntries != null) {
for (var entry : treeEntries) {
entries.add(entry.getName());
}
found = true;
}
}

if (isOutput(path)) {
try {
remoteOutputTree.getPath(path).getDirectoryEntries().stream()
.map(Path::getBaseName)
.forEach(entries::add);
found = true;
} catch (FileNotFoundException ignored) {
// Will be rethrown below if directory exists on neither side.
}
}

try {
localFs.getPath(path).getDirectoryEntries().stream()
.map(Path::getBaseName)
.forEach(entries::add);
} catch (FileNotFoundException e) {
if (!found) {
throw e;
}
}

// sort entries to get a deterministic order.
return ImmutableList.sortedCopyOf(entries);
protected Collection<String> getDirectoryEntries(PathFragment path) throws IOException {
return getDirectoryContents(path, /* followSymlinks= */ false, Dirent::getName);
}

@Override
protected Collection<Dirent> readdir(PathFragment path, boolean followSymlinks)
throws IOException {
HashMap<String, Dirent> entries = new HashMap<>();

boolean found = false;
return getDirectoryContents(path, followSymlinks, Function.identity());
}

private <T extends Comparable<T>> ImmutableSortedSet<T> getDirectoryContents(
PathFragment path, boolean followSymlinks, Function<Dirent, T> transformer)
throws IOException {
path = resolveSymbolicLinks(path).asFragment();

HashMap<String, Dirent> entries = new HashMap<>();
boolean found = false;

if (path.startsWith(execRoot)) {
var execPath = path.relativeTo(execRoot);
Collection<Dirent> treeEntries = inputTreeArtifactDirectoryCache.get(execPath);
Expand Down Expand Up @@ -851,8 +821,12 @@ protected Collection<Dirent> readdir(PathFragment path, boolean followSymlinks)
}
}

// sort entries to get a deterministic order.
return ImmutableList.sortedCopyOf(entries.values());
// Sort entries to get a deterministic order.
ImmutableSortedSet.Builder<T> builder = ImmutableSortedSet.naturalOrder();
for (var entry : entries.values()) {
builder.add(transformer.apply(entry));
}
return builder.build();
}

private Dirent maybeFollowSymlinkForDirent(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -606,7 +606,7 @@ public boolean exists(PathFragment path) {

/**
* Returns a collection containing the names of all entities within the directory denoted by the
* {@code path}.
* {@code path}. Symlinks are followed when resolving the directory whose entries are to be read.
*
* @throws IOException if there was an error reading the directory entries
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -676,7 +676,34 @@ public void readdir_fromMultipleSources() throws Exception {
}

@Test
public void readdir_followSymlinks(
public void readdir_followSymlinks_forDirectory(
@TestParameter FilesystemTestParam from, @TestParameter FilesystemTestParam to)
throws Exception {
RemoteActionFileSystem actionFs = (RemoteActionFileSystem) createActionFileSystem();
FileSystem fromFs = from.getFilesystem(actionFs);
FileSystem toFs = to.getFilesystem(actionFs);

PathFragment linkPath = getOutputPath("sym");
PathFragment targetPath = getOutputPath("dir");
PathFragment childPath = getOutputPath("dir/child");

fromFs.getPath(linkPath).createSymbolicLink(execRoot.getRelative(targetPath).asFragment());
toFs.getPath(targetPath).createDirectory();

if (toFs.equals(actionFs.getLocalFileSystem())) {
writeLocalFile(actionFs, childPath, "content");
} else {
injectRemoteFile(actionFs, childPath, "content");
}

assertReaddir(
actionFs, linkPath, /* followSymlinks= */ false, new Dirent("child", Dirent.Type.FILE));
assertReaddir(
actionFs, linkPath, /* followSymlinks= */ true, new Dirent("child", Dirent.Type.FILE));
}

@Test
public void readdir_followSymlinks_forDirectoryEntries(
@TestParameter FilesystemTestParam from, @TestParameter FilesystemTestParam to)
throws Exception {
RemoteActionFileSystem actionFs = (RemoteActionFileSystem) createActionFileSystem();
Expand Down Expand Up @@ -707,6 +734,17 @@ public void readdir_followSymlinks(
actionFs, dirPath, /* followSymlinks= */ true, new Dirent("sym", Dirent.Type.FILE));
}

@Test
public void readdir_nonDirectory() throws Exception {
RemoteActionFileSystem actionFs = (RemoteActionFileSystem) createActionFileSystem();
Artifact artifact = ActionsTestUtil.createArtifact(outputRoot, "dir/out");
PathFragment path = artifact.getPath().getParentDirectory().asFragment();

writeLocalFile(actionFs, path, "content");

assertReaddirThrows(actionFs, path, /* followSymlinks= */ true);
}

@Test
public void readdir_notFound() throws Exception {
RemoteActionFileSystem actionFs = (RemoteActionFileSystem) createActionFileSystem();
Expand Down
Loading