Skip to content

Commit b416193

Browse files
alexjskiphilwo
authored andcommitted
Allow using embedded tools in sandboxed spawn runners.
When using sandboxed runners, the only supported `VirtualActionInput` types are empty inputs and param files. Add support for `BinTools.PathActionInput` so that we can we can use embedded tools in sandboxed runners. Make sure to write them as executable to the sandbox -- contrary to param files, embedded tools can be run. PiperOrigin-RevId: 344438370
1 parent 7921fe0 commit b416193

File tree

4 files changed

+245
-29
lines changed

4 files changed

+245
-29
lines changed

src/main/java/com/google/devtools/build/lib/sandbox/SandboxHelpers.java

+34-28
Original file line numberDiff line numberDiff line change
@@ -15,12 +15,10 @@
1515
package com.google.devtools.build.lib.sandbox;
1616

1717
import com.google.auto.value.AutoValue;
18-
import com.google.common.base.Preconditions;
1918
import com.google.common.collect.ImmutableSet;
2019
import com.google.devtools.build.lib.actions.ActionInput;
2120
import com.google.devtools.build.lib.actions.Artifact;
2221
import com.google.devtools.build.lib.actions.Artifact.ArtifactExpander;
23-
import com.google.devtools.build.lib.actions.CommandLines.ParamFileActionInput;
2422
import com.google.devtools.build.lib.actions.Spawn;
2523
import com.google.devtools.build.lib.actions.cache.VirtualActionInput;
2624
import com.google.devtools.build.lib.actions.cache.VirtualActionInput.EmptyActionInput;
@@ -85,9 +83,7 @@ public static void atomicallyWriteVirtualInput(
8583
Path tmpPath = outputPath.getFileSystem().getPath(outputPath.getPathString() + uniqueSuffix);
8684
tmpPath.getParentDirectory().createDirectoryAndParents();
8785
try {
88-
try (OutputStream outputStream = tmpPath.getOutputStream()) {
89-
input.writeTo(outputStream);
90-
}
86+
writeVirtualInputTo(input, tmpPath);
9187
// We expect the following to replace the params file atomically in case we are using
9288
// the dynamic scheduler and we are racing the remote strategy writing this same file.
9389
tmpPath.renameTo(outputPath);
@@ -130,36 +126,34 @@ public Map<PathFragment, PathFragment> getSymlinks() {
130126
/**
131127
* Materializes a single virtual input inside the given execroot.
132128
*
129+
* <p>When materializing inputs under a new sandbox exec root, we can expect the input to not
130+
* exist, but we cannot make the same assumption for the non-sandboxed exec root therefore, we
131+
* may need to delete existing files.
132+
*
133133
* @param input virtual input to materialize
134134
* @param execroot path to the execroot under which to materialize the virtual input
135-
* @param needsDelete whether to attempt to delete a previous instance of this virtual input.
136-
* When materializing under a new sandbox execroot, we can expect the input to not exist,
137-
* but we cannot make the same assumption for the non-sandboxed execroot.
135+
* @param isExecRootSandboxed whether the execroot is sandboxed.
138136
* @throws IOException if the virtual input cannot be materialized
139137
*/
140138
private static void materializeVirtualInput(
141-
VirtualActionInput input, Path execroot, boolean needsDelete) throws IOException {
142-
if (input instanceof ParamFileActionInput) {
143-
ParamFileActionInput paramFileInput = (ParamFileActionInput) input;
144-
Path outputPath = execroot.getRelative(paramFileInput.getExecPath());
145-
if (needsDelete) {
146-
if (outputPath.exists()) {
147-
outputPath.delete();
148-
}
149-
150-
outputPath.getParentDirectory().createDirectoryAndParents();
151-
try (OutputStream outputStream = outputPath.getOutputStream()) {
152-
paramFileInput.writeTo(outputStream);
153-
}
154-
} else {
155-
atomicallyWriteVirtualInput(paramFileInput, outputPath, ".sandbox");
156-
}
157-
} else {
139+
VirtualActionInput input, Path execroot, boolean isExecRootSandboxed) throws IOException {
140+
if (input instanceof EmptyActionInput) {
158141
// TODO(b/150963503): We can turn this into an unreachable code path when the old
159-
// !delayVirtualInputMaterialization code path is deleted.
160-
// TODO(ulfjack): Handle all virtual inputs, e.g., by writing them to a file.
161-
Preconditions.checkState(input instanceof EmptyActionInput);
142+
// !delayVirtualInputMaterialization code path is deleted.
143+
return;
162144
}
145+
146+
Path outputPath = execroot.getRelative(input.getExecPath());
147+
if (isExecRootSandboxed) {
148+
atomicallyWriteVirtualInput(input, outputPath, ".sandbox");
149+
return;
150+
}
151+
152+
if (outputPath.exists()) {
153+
outputPath.delete();
154+
}
155+
outputPath.getParentDirectory().createDirectoryAndParents();
156+
writeVirtualInputTo(input, outputPath);
163157
}
164158

165159
/**
@@ -179,6 +173,18 @@ public void materializeVirtualInputs(Path sandboxExecRoot) throws IOException {
179173
}
180174
}
181175

176+
private static void writeVirtualInputTo(VirtualActionInput input, Path target)
177+
throws IOException {
178+
try (OutputStream out = target.getOutputStream()) {
179+
input.writeTo(out);
180+
}
181+
// Some of the virtual inputs can be executed, e.g. embedded tools. Setting executable flag for
182+
// other is fine since that is only more permissive. Please note that for action outputs (e.g.
183+
// file write, where the user can specify executable flag), we will have artifacts which do not
184+
// go through this code path.
185+
target.setExecutable(true);
186+
}
187+
182188
/**
183189
* Returns the inputs of a Spawn as a map of PathFragments relative to an execRoot to paths in the
184190
* host filesystem where the input files can be found.

src/test/java/com/google/devtools/build/lib/remote/RemoteActionInputFetcherTest.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -125,7 +125,7 @@ public void testStagingVirtualActionInput() throws Exception {
125125
// assert
126126
Path p = execRoot.getRelative(a.getExecPath());
127127
assertThat(FileSystemUtils.readContent(p, StandardCharsets.UTF_8)).isEqualTo("hello world");
128-
assertThat(p.isExecutable()).isFalse();
128+
assertThat(p.isExecutable()).isTrue();
129129
assertThat(actionInputFetcher.downloadedFiles()).isEmpty();
130130
assertThat(actionInputFetcher.downloadsInProgress()).isEmpty();
131131
}

src/test/java/com/google/devtools/build/lib/sandbox/BUILD

+6
Original file line numberDiff line numberDiff line change
@@ -54,11 +54,17 @@ java_test(
5454
deps = [
5555
":sandboxfs-base-tests",
5656
":testutil",
57+
"//src/main/java/com/google/devtools/build/lib/actions",
58+
"//src/main/java/com/google/devtools/build/lib/actions:artifacts",
59+
"//src/main/java/com/google/devtools/build/lib/exec:bin_tools",
5760
"//src/main/java/com/google/devtools/build/lib/sandbox",
5861
"//src/main/java/com/google/devtools/build/lib/vfs",
5962
"//src/main/java/com/google/devtools/build/lib/vfs:pathfragment",
6063
"//src/main/java/com/google/devtools/build/lib/vfs/inmemoryfs",
6164
"//src/main/java/com/google/devtools/common/options",
65+
"//src/test/java/com/google/devtools/build/lib/actions/util",
66+
"//src/test/java/com/google/devtools/build/lib/exec/util",
67+
"//src/test/java/com/google/devtools/build/lib/testutil",
6268
"//src/test/java/com/google/devtools/build/lib/testutil:TestUtils",
6369
"//third_party:guava",
6470
"//third_party:junit4",
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,204 @@
1+
// Copyright 2020 The Bazel Authors. All rights reserved.
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package com.google.devtools.build.lib.sandbox;
16+
17+
import static com.google.common.collect.ImmutableMap.toImmutableMap;
18+
import static com.google.common.truth.Truth.assertThat;
19+
import static java.nio.charset.StandardCharsets.UTF_8;
20+
21+
import com.google.common.collect.ImmutableList;
22+
import com.google.common.collect.ImmutableMap;
23+
import com.google.devtools.build.lib.actions.ActionInput;
24+
import com.google.devtools.build.lib.actions.Artifact.ArtifactExpander;
25+
import com.google.devtools.build.lib.actions.CommandLines.ParamFileActionInput;
26+
import com.google.devtools.build.lib.actions.ParameterFile.ParameterFileType;
27+
import com.google.devtools.build.lib.actions.Spawn;
28+
import com.google.devtools.build.lib.actions.cache.VirtualActionInput;
29+
import com.google.devtools.build.lib.actions.util.ActionsTestUtil;
30+
import com.google.devtools.build.lib.exec.BinTools;
31+
import com.google.devtools.build.lib.exec.util.SpawnBuilder;
32+
import com.google.devtools.build.lib.sandbox.SandboxHelpers.SandboxInputs;
33+
import com.google.devtools.build.lib.testutil.Scratch;
34+
import com.google.devtools.build.lib.vfs.Dirent;
35+
import com.google.devtools.build.lib.vfs.FileSystemUtils;
36+
import com.google.devtools.build.lib.vfs.Path;
37+
import com.google.devtools.build.lib.vfs.PathFragment;
38+
import com.google.devtools.build.lib.vfs.Symlinks;
39+
import java.io.IOException;
40+
import java.util.Arrays;
41+
import java.util.function.Function;
42+
import org.junit.Before;
43+
import org.junit.Test;
44+
import org.junit.runner.RunWith;
45+
import org.junit.runners.JUnit4;
46+
47+
/** Tests for {@link SandboxHelpers}. */
48+
@RunWith(JUnit4.class)
49+
public class SandboxHelpersTest {
50+
51+
private static final ArtifactExpander EMPTY_EXPANDER = (ignored1, ignored2) -> {};
52+
private static final Spawn SPAWN = new SpawnBuilder().build();
53+
54+
private final Scratch scratch = new Scratch();
55+
private Path execRoot;
56+
57+
@Before
58+
public void createExecRoot() throws IOException {
59+
execRoot = scratch.dir("/execRoot");
60+
}
61+
62+
@Test
63+
public void processInputFiles_materializesParamFile() throws Exception {
64+
SandboxHelpers sandboxHelpers = new SandboxHelpers(/*delayVirtualInputMaterialization=*/ false);
65+
ParamFileActionInput paramFile =
66+
new ParamFileActionInput(
67+
PathFragment.create("paramFile"),
68+
ImmutableList.of("-a", "-b"),
69+
ParameterFileType.UNQUOTED,
70+
UTF_8);
71+
72+
SandboxInputs inputs =
73+
sandboxHelpers.processInputFiles(inputMap(paramFile), SPAWN, EMPTY_EXPANDER, execRoot);
74+
75+
assertThat(inputs.getFiles())
76+
.containsExactly(PathFragment.create("paramFile"), execRoot.getChild("paramFile"));
77+
assertThat(inputs.getSymlinks()).isEmpty();
78+
assertThat(FileSystemUtils.readLines(execRoot.getChild("paramFile"), UTF_8))
79+
.containsExactly("-a", "-b")
80+
.inOrder();
81+
assertThat(execRoot.getChild("paramFile").isExecutable()).isTrue();
82+
}
83+
84+
@Test
85+
public void processInputFiles_materializesBinToolsFile() throws Exception {
86+
SandboxHelpers sandboxHelpers = new SandboxHelpers(/*delayVirtualInputMaterialization=*/ false);
87+
BinTools.PathActionInput tool =
88+
new BinTools.PathActionInput(
89+
scratch.file("tool", "#!/bin/bash", "echo hello"),
90+
PathFragment.create("_bin/say_hello"));
91+
92+
SandboxInputs inputs =
93+
sandboxHelpers.processInputFiles(inputMap(tool), SPAWN, EMPTY_EXPANDER, execRoot);
94+
95+
assertThat(inputs.getFiles())
96+
.containsExactly(
97+
PathFragment.create("_bin/say_hello"), execRoot.getRelative("_bin/say_hello"));
98+
assertThat(inputs.getSymlinks()).isEmpty();
99+
assertThat(FileSystemUtils.readLines(execRoot.getRelative("_bin/say_hello"), UTF_8))
100+
.containsExactly("#!/bin/bash", "echo hello")
101+
.inOrder();
102+
assertThat(execRoot.getRelative("_bin/say_hello").isExecutable()).isTrue();
103+
}
104+
105+
@Test
106+
public void processInputFiles_delayVirtualInputMaterialization_doesNotStoreVirtualInput()
107+
throws Exception {
108+
SandboxHelpers sandboxHelpers = new SandboxHelpers(/*delayVirtualInputMaterialization=*/ true);
109+
ParamFileActionInput paramFile =
110+
new ParamFileActionInput(
111+
PathFragment.create("paramFile"),
112+
ImmutableList.of("-a", "-b"),
113+
ParameterFileType.UNQUOTED,
114+
UTF_8);
115+
116+
SandboxInputs inputs =
117+
sandboxHelpers.processInputFiles(inputMap(paramFile), SPAWN, EMPTY_EXPANDER, execRoot);
118+
119+
assertThat(inputs.getFiles()).isEmpty();
120+
assertThat(inputs.getSymlinks()).isEmpty();
121+
assertThat(execRoot.getChild("paramFile").exists()).isFalse();
122+
}
123+
124+
@Test
125+
public void sandboxInputMaterializeVirtualInputs_delayMaterialization_writesCorrectFiles()
126+
throws Exception {
127+
SandboxHelpers sandboxHelpers = new SandboxHelpers(/*delayVirtualInputMaterialization=*/ true);
128+
ParamFileActionInput paramFile =
129+
new ParamFileActionInput(
130+
PathFragment.create("paramFile"),
131+
ImmutableList.of("-a", "-b"),
132+
ParameterFileType.UNQUOTED,
133+
UTF_8);
134+
BinTools.PathActionInput tool =
135+
new BinTools.PathActionInput(
136+
scratch.file("tool", "tool_code"), PathFragment.create("tools/tool"));
137+
SandboxInputs inputs =
138+
sandboxHelpers.processInputFiles(
139+
inputMap(paramFile, tool), SPAWN, EMPTY_EXPANDER, execRoot);
140+
141+
inputs.materializeVirtualInputs(scratch.dir("/sandbox"));
142+
143+
Path sandboxParamFile = scratch.resolve("/sandbox/paramFile");
144+
assertThat(FileSystemUtils.readLines(sandboxParamFile, UTF_8))
145+
.containsExactly("-a", "-b")
146+
.inOrder();
147+
assertThat(sandboxParamFile.isExecutable()).isTrue();
148+
Path sandboxToolFile = scratch.resolve("/sandbox/tools/tool");
149+
assertThat(FileSystemUtils.readLines(sandboxToolFile, UTF_8)).containsExactly("tool_code");
150+
assertThat(sandboxToolFile.isExecutable()).isTrue();
151+
}
152+
153+
private static ImmutableMap<PathFragment, ActionInput> inputMap(ActionInput... inputs) {
154+
return Arrays.stream(inputs)
155+
.collect(toImmutableMap(ActionInput::getExecPath, Function.identity()));
156+
}
157+
158+
@Test
159+
public void atomicallyWriteVirtualInput_writesParamFile() throws Exception {
160+
ParamFileActionInput paramFile =
161+
new ParamFileActionInput(
162+
PathFragment.create("paramFile"),
163+
ImmutableList.of("-a", "-b"),
164+
ParameterFileType.UNQUOTED,
165+
UTF_8);
166+
167+
SandboxHelpers.atomicallyWriteVirtualInput(
168+
paramFile, scratch.resolve("/outputs/paramFile"), "-1234");
169+
170+
assertThat(scratch.resolve("/outputs").readdir(Symlinks.NOFOLLOW))
171+
.containsExactly(new Dirent("paramFile", Dirent.Type.FILE));
172+
Path outputFile = scratch.resolve("/outputs/paramFile");
173+
assertThat(FileSystemUtils.readLines(outputFile, UTF_8)).containsExactly("-a", "-b").inOrder();
174+
assertThat(outputFile.isExecutable()).isTrue();
175+
}
176+
177+
@Test
178+
public void atomicallyWriteVirtualInput_writesBinToolsFile() throws Exception {
179+
BinTools.PathActionInput tool =
180+
new BinTools.PathActionInput(
181+
scratch.file("tool", "tool_code"), PathFragment.create("tools/tool"));
182+
183+
SandboxHelpers.atomicallyWriteVirtualInput(tool, scratch.resolve("/outputs/tool"), "-1234");
184+
185+
assertThat(scratch.resolve("/outputs").readdir(Symlinks.NOFOLLOW))
186+
.containsExactly(new Dirent("tool", Dirent.Type.FILE));
187+
Path outputFile = scratch.resolve("/outputs/tool");
188+
assertThat(FileSystemUtils.readLines(outputFile, UTF_8)).containsExactly("tool_code");
189+
assertThat(outputFile.isExecutable()).isTrue();
190+
}
191+
192+
@Test
193+
public void atomicallyWriteVirtualInput_writesArbitraryVirtualInput() throws Exception {
194+
VirtualActionInput input = ActionsTestUtil.createVirtualActionInput("file", "hello");
195+
196+
SandboxHelpers.atomicallyWriteVirtualInput(input, scratch.resolve("/outputs/file"), "-1234");
197+
198+
assertThat(scratch.resolve("/outputs").readdir(Symlinks.NOFOLLOW))
199+
.containsExactly(new Dirent("file", Dirent.Type.FILE));
200+
Path outputFile = scratch.resolve("/outputs/file");
201+
assertThat(FileSystemUtils.readLines(outputFile, UTF_8)).containsExactly("hello");
202+
assertThat(outputFile.isExecutable()).isTrue();
203+
}
204+
}

0 commit comments

Comments
 (0)