Skip to content

Commit

Permalink
Implement RemoteActionFileSystem#statIfFound correctly when the path …
Browse files Browse the repository at this point in the history
…cannot be canonicalized (because one of the components is a non-directory or a dangling symlink).

This improves the error message for a tree artifact containing a dangling symlink, which regressed in bazelbuild@4247c20 (see bazelbuild#15454 (comment)).

PiperOrigin-RevId: 617870632
Change-Id: I6847084a52b1e4bb7d8a9384ad6cd5d015dddf1b
  • Loading branch information
tjgq authored and bazel-io committed Mar 25, 2024
1 parent 51f869e commit f5c9de9
Show file tree
Hide file tree
Showing 7 changed files with 69 additions and 34 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -630,13 +630,17 @@ protected FileStatus statNullable(PathFragment path, boolean followSymlinks) {
private FileStatus statInternal(PathFragment path, FollowMode followMode, StatSources statSources)
throws IOException {
// Canonicalize the path.
if (followMode == FollowMode.FOLLOW_ALL) {
path = resolveSymbolicLinks(path).asFragment();
} else if (followMode == FollowMode.FOLLOW_PARENT) {
PathFragment parent = path.getParentDirectory();
if (parent != null) {
path = resolveSymbolicLinks(parent).asFragment().getChild(path.getBaseName());
try {
if (followMode == FollowMode.FOLLOW_ALL) {
path = resolveSymbolicLinks(path).asFragment();
} else if (followMode == FollowMode.FOLLOW_PARENT) {
PathFragment parent = path.getParentDirectory();
if (parent != null) {
path = resolveSymbolicLinks(parent).asFragment().getChild(path.getBaseName());
}
}
} catch (FileNotFoundException e) {
return null;
}

// Since the path has been canonicalized, the operations below never need to follow symlinks.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1488,11 +1488,12 @@ private static void reportOutputTreeArtifactErrors(
Action action, Artifact output, Reporter reporter, IOException e) {
String errorMessage;
if (e instanceof FileNotFoundException) {
errorMessage = String.format("TreeArtifact %s was not created", output.prettyPrint());
errorMessage = String.format("output tree artifact %s was not created", output.prettyPrint());
} else {
errorMessage =
String.format(
"Error while validating output TreeArtifact %s : %s", output, e.getMessage());
"error while validating output tree artifact %s: %s",
output.prettyPrint(), e.getMessage());
}

reporter.handle(Event.error(action.getOwner().getLocation(), errorMessage));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -558,9 +558,7 @@ private void visit(

if (statFollow == null) {
throw new IOException(
String.format(
"Child %s of tree artifact %s is a dangling symbolic link",
parentRelativePath, parentDir));
String.format("child %s is a dangling symbolic link", parentRelativePath));
}

if (statFollow.isFile() && !statFollow.isSpecialFile()) {
Expand All @@ -574,9 +572,7 @@ private void visit(

if (type == Dirent.Type.UNKNOWN) {
throw new IOException(
String.format(
"Child %s of tree artifact %s has an unsupported type",
parentRelativePath, parentDir));
String.format("child %s has an unsupported type", parentRelativePath));
}

visitor.visit(parentRelativePath, type, traversedSymlink);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -451,11 +451,43 @@ public void statAndExists_followSymlinks(
@Test
public void statAndExists_notFound() throws Exception {
RemoteActionFileSystem actionFs = (RemoteActionFileSystem) createActionFileSystem();
Artifact artifact = ActionsTestUtil.createArtifact(outputRoot, "out");
PathFragment path = artifact.getPath().asFragment();
PathFragment path = getOutputPath("does_not_exist");

assertThat(actionFs.exists(path)).isFalse();

assertThat(actionFs.statIfFound(path, /* followSymlinks= */ true)).isNull();

assertThrows(
FileNotFoundException.class, () -> actionFs.stat(path, /* followSymlinks= */ true));
}

@Test
public void statAndExists_isNotDirectory() throws Exception {
RemoteActionFileSystem actionFs = (RemoteActionFileSystem) createActionFileSystem();
PathFragment nonDirPath = getOutputPath("non_dir");
PathFragment path = nonDirPath.getChild("file");

writeLocalFile(actionFs, nonDirPath, "content");

assertThat(actionFs.exists(path)).isFalse();

assertThat(actionFs.statIfFound(path, /* followSymlinks= */ true)).isNull();

assertThrows(
FileNotFoundException.class, () -> actionFs.stat(path, /* followSymlinks= */ true));
}

@Test
public void statAndExists_danglingSymlink_notFound() throws Exception {
RemoteActionFileSystem actionFs = (RemoteActionFileSystem) createActionFileSystem();
PathFragment path = getOutputPath("sym");

actionFs.getPath(path).createSymbolicLink(PathFragment.create("/does_not_exist"));

assertThat(actionFs.exists(path)).isFalse();

assertThat(actionFs.statIfFound(path, /* followSymlinks= */ true)).isNull();

assertThrows(
FileNotFoundException.class, () -> actionFs.stat(path, /* followSymlinks= */ true));
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -259,6 +259,20 @@ public void createsTreeArtifactValueFromFilesystem() throws Exception {
assertThat(chmodCalls).isEmpty();
}

@Test
public void withDanglingSymlinkInTreeArtifactFailsWithException() throws Exception {
SpecialArtifact treeArtifact =
ActionsTestUtil.createTreeArtifactWithGeneratingAction(outputRoot, "foo/bar");
TreeFileArtifact child = TreeFileArtifact.createTreeOutput(treeArtifact, "child");
treeArtifact.getPath().createDirectoryAndParents();
child.getPath().createSymbolicLink(PathFragment.create("/does_not_exist"));

ActionOutputMetadataStore store = createStore(/* outputs= */ ImmutableSet.of(treeArtifact));

IOException e = assertThrows(IOException.class, () -> store.getOutputMetadata(treeArtifact));
assertThat(e).hasMessageThat().contains("dangling symbolic link");
}

@Test
public void resettingOutputs() throws Exception {
PathFragment path = PathFragment.create("foo/bar");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -505,9 +505,7 @@ void run(ActionExecutionContext actionExecutionContext) throws IOException {

ImmutableList<Event> errors = ImmutableList.copyOf(eventCollector);
assertThat(errors).hasSize(2);
assertThat(errors.get(0).getMessage())
.contains(
"Child links/link of tree artifact " + out.getPath() + " is a dangling symbolic link");
assertThat(errors.get(0).getMessage()).contains("child links/link is a dangling symbolic link");
assertThat(errors.get(1).getMessage()).contains("not all outputs were created or valid");
}

Expand Down Expand Up @@ -555,9 +553,7 @@ void run(ActionExecutionContext actionExecutionContext) throws IOException {

ImmutableList<Event> errors = ImmutableList.copyOf(eventCollector);
assertThat(errors).hasSize(2);
assertThat(errors.get(0).getMessage())
.contains(
"Child links/link of tree artifact " + out.getPath() + " is a dangling symbolic link");
assertThat(errors.get(0).getMessage()).contains("child links/link is a dangling symbolic link");
assertThat(errors.get(1).getMessage()).contains("not all outputs were created or valid");
}

Expand Down Expand Up @@ -607,9 +603,7 @@ void run(ActionExecutionContext actionExecutionContext) throws IOException {

ImmutableList<Event> errors = ImmutableList.copyOf(eventCollector);
assertThat(errors).hasSize(2);
assertThat(errors.get(0).getMessage())
.contains(
"Child links/link of tree artifact " + out.getPath() + " is a dangling symbolic link");
assertThat(errors.get(0).getMessage()).contains("child links/link is a dangling symbolic link");
assertThat(errors.get(1).getMessage()).contains("not all outputs were created or valid");
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -419,9 +419,7 @@ public void visitTree_throwsOnDanglingSymlink() throws Exception {
assertThrows(
IOException.class,
() -> TreeArtifactValue.visitTree(treeDir, (child, type, traversedSymlink) -> {}));
assertThat(e)
.hasMessageThat()
.contains("Child symlink of tree artifact /tree is a dangling symbolic link");
assertThat(e).hasMessageThat().contains("child symlink is a dangling symbolic link");
}

@Test
Expand Down Expand Up @@ -456,9 +454,7 @@ public Collection<Dirent> readdir(PathFragment path, boolean followSymlinks)
assertThrows(
IOException.class,
() -> TreeArtifactValue.visitTree(treeDir, (child, type, traversedSymlink) -> {}));
assertThat(e)
.hasMessageThat()
.contains("Child unknown of tree artifact /tree has an unsupported type");
assertThat(e).hasMessageThat().contains("child unknown has an unsupported type");
}

@Test
Expand Down Expand Up @@ -523,9 +519,7 @@ public long getSize() {
assertThrows(
IOException.class,
() -> TreeArtifactValue.visitTree(treeDir, (child, type, traversedSymlink) -> {}));
assertThat(e)
.hasMessageThat()
.contains("Child sym of tree artifact /tree has an unsupported type");
assertThat(e).hasMessageThat().contains("child sym has an unsupported type");
}

@Test
Expand Down

0 comments on commit f5c9de9

Please sign in to comment.