Skip to content

Commit d98b02e

Browse files
authored
[7.2.0] Preserve paths under hermetic /tmp in the sandbox (#22407)
The bind mounting scheme used with the Linux sandbox' hermetic `/tmp` feature is modified to preserve all paths as they are outside the sandbox, which removes the need to rewrite paths when staging inputs into and, crucially, moving outputs out of the sandbox. Source roots and output base paths under `/tmp` are now treated just like any user-specified bind mount under `/tmp`: They are mounted under the hermetic tmp directory with their path relativized against `/tmp` before the hermetic tmp directory is mounted as `/tmp` as the final step. There is one caveat compared to user-specified mounts: Source roots, which may themselves not lie under `/tmp`, can be symlinks to directories under `/tmp` (e.g., when they arise from a `local_repository`). To handle this situation in the common case, all parent directories of package path entries (up to direct children of `/tmp`) are mounted into the sandbox. If users use `local_repository`s with fixed target paths under `/tmp`, they will need to specify `--sandbox_add_mount_pair`. Overlayfs has been considered as an alternative to this approach, but ultimately doesn't seem to work for this use case since its `lowerpath`, which would be `/tmp`, is not allowed to have child mounts from a different user namespace (see https://unix.stackexchange.com/questions/776030/mounting-overlayfs-in-a-user-namespace-with-child-mounts). However, this is exactly the situation created by a Bazel-in-Bazel test and can also arise if the user has existing mounts under `/tmp` when using Bazel (e.g. the JetBrains toolbox on Linux uses such mounts). This replaces and mostly reverts the following commits, but keeps their tests: * bf6ebe9 * fb6658c * bc1d9d3 * 1829883 * 70691f2 * a556969 * 8e32f44 (had its test lost in an incorrect merge conflict resolution, this PR adds it back) Fixes #20533 Work towards #20753 Fixes #21215 Fixes #22117 Fixes #22226 Fixes #22290 RELNOTES: Paths in the Linux sandbox are now again identical to those outside the sandbox, even with `--incompatible_sandbox_hermetic_tmp`. Closes #22001. PiperOrigin-RevId: 634381503 Change-Id: I9f7f3948c705be120c55c9b0c51204e5bea45f61 Fixes #22291
1 parent d198558 commit d98b02e

25 files changed

+549
-794
lines changed

src/main/java/com/google/devtools/build/lib/actions/FileArtifactValue.java

Lines changed: 7 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -170,19 +170,11 @@ protected boolean couldBeModifiedByMetadata(FileArtifactValue lastKnown) {
170170
/**
171171
* Optional materialization path.
172172
*
173-
* <p>If present, this artifact is a copy of another artifact whose contents live at this path.
174-
* This can happen when it is declared as a file and not as an unresolved symlink but the action
175-
* that creates it materializes it in the filesystem as a symlink to another output artifact. This
176-
* information is useful in two situations:
177-
*
178-
* <ol>
179-
* <li>When the symlink target is a remotely stored artifact, we can avoid downloading it
180-
* multiple times when building without the bytes (see AbstractActionInputPrefetcher).
181-
* <li>When the symlink target is inaccessible from the sandboxed environment an action runs
182-
* under, we can rewrite it accordingly (see SandboxHelpers).
183-
* </ol>
184-
*
185-
* @see com.google.devtools.build.lib.skyframe.TreeArtifactValue#getMaterializationExecPath().
173+
* <p>If present, this artifact is a copy of another artifact. It is still tracked as a
174+
* non-symlink by Bazel, but materialized in the local filesystem as a symlink to the original
175+
* artifact, whose contents live at this location. This is used by {@link
176+
* com.google.devtools.build.lib.remote.AbstractActionInputPrefetcher} to implement zero-cost
177+
* copies of remotely stored artifacts.
186178
*/
187179
public Optional<PathFragment> getMaterializationExecPath() {
188180
return Optional.empty();
@@ -223,12 +215,6 @@ public static FileArtifactValue createForSourceArtifact(
223215
xattrProvider);
224216
}
225217

226-
public static FileArtifactValue createForResolvedSymlink(
227-
PathFragment realPath, FileArtifactValue metadata, @Nullable byte[] digest) {
228-
return new ResolvedSymlinkFileArtifactValue(
229-
realPath, digest, metadata.getContentsProxy(), metadata.getSize());
230-
}
231-
232218
public static FileArtifactValue createFromInjectedDigest(
233219
FileArtifactValue metadata, @Nullable byte[] digest) {
234220
return createForNormalFile(digest, metadata.getContentsProxy(), metadata.getSize());
@@ -459,25 +445,7 @@ public String toString() {
459445
}
460446
}
461447

462-
private static final class ResolvedSymlinkFileArtifactValue extends RegularFileArtifactValue {
463-
private final PathFragment realPath;
464-
465-
private ResolvedSymlinkFileArtifactValue(
466-
PathFragment realPath,
467-
@Nullable byte[] digest,
468-
@Nullable FileContentsProxy proxy,
469-
long size) {
470-
super(digest, proxy, size);
471-
this.realPath = realPath;
472-
}
473-
474-
@Override
475-
public Optional<PathFragment> getMaterializationExecPath() {
476-
return Optional.of(realPath);
477-
}
478-
}
479-
480-
private static class RegularFileArtifactValue extends FileArtifactValue {
448+
private static final class RegularFileArtifactValue extends FileArtifactValue {
481449
private final byte[] digest;
482450
@Nullable private final FileContentsProxy proxy;
483451
private final long size;
@@ -500,8 +468,7 @@ public boolean equals(Object o) {
500468
RegularFileArtifactValue that = (RegularFileArtifactValue) o;
501469
return Arrays.equals(digest, that.digest)
502470
&& Objects.equals(proxy, that.proxy)
503-
&& size == that.size
504-
&& Objects.equals(getMaterializationExecPath(), that.getMaterializationExecPath());
471+
&& size == that.size;
505472
}
506473

507474
@Override

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

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,6 @@
2727
import com.google.devtools.build.lib.vfs.FileSystemUtils;
2828
import com.google.devtools.build.lib.vfs.Path;
2929
import com.google.devtools.build.lib.vfs.PathFragment;
30-
import com.google.devtools.build.lib.vfs.RootedPath;
3130
import java.io.IOException;
3231
import java.util.LinkedHashSet;
3332
import java.util.Set;
@@ -163,9 +162,9 @@ void createInputs(Iterable<PathFragment> inputsToCreate, SandboxInputs inputs)
163162
}
164163
Path key = sandboxExecRoot.getRelative(fragment);
165164
if (inputs.getFiles().containsKey(fragment)) {
166-
RootedPath fileDest = inputs.getFiles().get(fragment);
165+
Path fileDest = inputs.getFiles().get(fragment);
167166
if (fileDest != null) {
168-
copyFile(fileDest.asPath(), key);
167+
copyFile(fileDest, key);
169168
} else {
170169
FileSystemUtils.createEmptyFile(key);
171170
}

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

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -360,21 +360,19 @@ private boolean wasTimeout(Duration timeout, Duration wallTime) {
360360
/**
361361
* Gets the list of directories that the spawn will assume to be writable.
362362
*
363-
* @param sandboxExecRoot the exec root of the sandbox from the point of view of the Bazel process
364-
* @param withinSandboxExecRoot the exec root from the point of view of the sandboxed processes
363+
* @param sandboxExecRoot the exec root of the sandbox
365364
* @param env the environment of the sandboxed processes
366365
* @throws IOException because we might resolve symlinks, which throws {@link IOException}.
367366
*/
368-
protected ImmutableSet<Path> getWritableDirs(
369-
Path sandboxExecRoot, Path withinSandboxExecRoot, Map<String, String> env)
367+
protected ImmutableSet<Path> getWritableDirs(Path sandboxExecRoot, Map<String, String> env)
370368
throws IOException {
371369
// We have to make the TEST_TMPDIR directory writable if it is specified.
372370
ImmutableSet.Builder<Path> writablePaths = ImmutableSet.builder();
373371

374372
// On Windows, sandboxExecRoot is actually the main execroot. We will specify
375373
// exactly which output path is writable.
376374
if (OS.getCurrent() != OS.WINDOWS) {
377-
writablePaths.add(withinSandboxExecRoot);
375+
writablePaths.add(execRoot);
378376
}
379377

380378
String testTmpdir = env.get("TEST_TMPDIR");

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

Lines changed: 2 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,6 @@
3737
import com.google.devtools.build.lib.vfs.FileSystem;
3838
import com.google.devtools.build.lib.vfs.Path;
3939
import com.google.devtools.build.lib.vfs.PathFragment;
40-
import com.google.devtools.build.lib.vfs.Root;
4140
import java.io.BufferedWriter;
4241
import java.io.File;
4342
import java.io.IOException;
@@ -103,7 +102,6 @@ private static boolean computeIsSupported() throws InterruptedException {
103102

104103
private final SandboxHelpers helpers;
105104
private final Path execRoot;
106-
private final ImmutableList<Root> packageRoots;
107105
private final boolean allowNetwork;
108106
private final ProcessWrapper processWrapper;
109107
private final Path sandboxBase;
@@ -134,7 +132,6 @@ private static boolean computeIsSupported() throws InterruptedException {
134132
super(cmdEnv);
135133
this.helpers = helpers;
136134
this.execRoot = cmdEnv.getExecRoot();
137-
this.packageRoots = cmdEnv.getPackageLocator().getPathEntries();
138135
this.allowNetwork = helpers.shouldAllowNetwork(cmdEnv.getOptions());
139136
this.alwaysWritableDirs = getAlwaysWritableDirs(cmdEnv.getRuntime().getFileSystem());
140137
this.processWrapper = ProcessWrapper.fromCommandEnvironment(cmdEnv);
@@ -221,18 +218,13 @@ protected SandboxedSpawn prepareSpawn(Spawn spawn, SpawnExecutionContext context
221218
localEnvProvider.rewriteLocalEnv(spawn.getEnvironment(), binTools, "/tmp");
222219

223220
final HashSet<Path> writableDirs = new HashSet<>(alwaysWritableDirs);
224-
ImmutableSet<Path> extraWritableDirs =
225-
getWritableDirs(sandboxExecRoot, sandboxExecRoot, environment);
221+
ImmutableSet<Path> extraWritableDirs = getWritableDirs(sandboxExecRoot, environment);
226222
writableDirs.addAll(extraWritableDirs);
227223

228224
SandboxInputs inputs =
229225
helpers.processInputFiles(
230226
context.getInputMapping(PathFragment.EMPTY_FRAGMENT, /* willAccessRepeatedly= */ true),
231-
context.getInputMetadataProvider(),
232-
execRoot,
233-
execRoot,
234-
packageRoots,
235-
null);
227+
execRoot);
236228
SandboxOutputs outputs = helpers.getOutputs(spawn);
237229

238230
final Path sandboxConfigPath = sandboxPath.getRelative("sandbox.sb");

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

Lines changed: 1 addition & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,6 @@
4545
import com.google.devtools.build.lib.util.ProcessUtils;
4646
import com.google.devtools.build.lib.vfs.Path;
4747
import com.google.devtools.build.lib.vfs.PathFragment;
48-
import com.google.devtools.build.lib.vfs.Root;
4948
import java.io.ByteArrayInputStream;
5049
import java.io.ByteArrayOutputStream;
5150
import java.io.IOException;
@@ -143,7 +142,6 @@ public static boolean isSupported(CommandEnvironment cmdEnv, Path dockerClient)
143142

144143
private final SandboxHelpers helpers;
145144
private final Path execRoot;
146-
private final ImmutableList<Root> packageRoots;
147145
private final boolean allowNetwork;
148146
private final Path dockerClient;
149147
private final ProcessWrapper processWrapper;
@@ -181,7 +179,6 @@ public static boolean isSupported(CommandEnvironment cmdEnv, Path dockerClient)
181179
super(cmdEnv);
182180
this.helpers = helpers;
183181
this.execRoot = cmdEnv.getExecRoot();
184-
this.packageRoots = cmdEnv.getPackageLocator().getPathEntries();
185182
this.allowNetwork = helpers.shouldAllowNetwork(cmdEnv.getOptions());
186183
this.dockerClient = dockerClient;
187184
this.processWrapper = ProcessWrapper.fromCommandEnvironment(cmdEnv);
@@ -226,11 +223,7 @@ protected SandboxedSpawn prepareSpawn(Spawn spawn, SpawnExecutionContext context
226223
SandboxInputs inputs =
227224
helpers.processInputFiles(
228225
context.getInputMapping(PathFragment.EMPTY_FRAGMENT, /* willAccessRepeatedly= */ true),
229-
context.getInputMetadataProvider(),
230-
execRoot,
231-
execRoot,
232-
packageRoots,
233-
null);
226+
execRoot);
234227
SandboxOutputs outputs = helpers.getOutputs(spawn);
235228

236229
Duration timeout = context.getTimeout();

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

Lines changed: 8 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,9 @@
1717
import static com.google.devtools.build.lib.sandbox.LinuxSandboxCommandLineBuilder.NetworkNamespace.NETNS;
1818
import static com.google.devtools.build.lib.sandbox.LinuxSandboxCommandLineBuilder.NetworkNamespace.NETNS_WITH_LOOPBACK;
1919

20-
import com.google.auto.value.AutoValue;
2120
import com.google.common.base.Preconditions;
2221
import com.google.common.collect.ImmutableList;
22+
import com.google.common.collect.ImmutableMap;
2323
import com.google.common.collect.ImmutableSet;
2424
import com.google.devtools.build.lib.actions.ExecutionRequirements;
2525
import com.google.devtools.build.lib.vfs.Path;
@@ -36,20 +36,6 @@
3636
* linux-sandbox} tool.
3737
*/
3838
public class LinuxSandboxCommandLineBuilder {
39-
/** A bind mount that needs to be present when the sandboxed command runs. */
40-
@AutoValue
41-
public abstract static class BindMount {
42-
public static BindMount of(Path mountPoint, Path source) {
43-
return new AutoValue_LinuxSandboxCommandLineBuilder_BindMount(mountPoint, source);
44-
}
45-
46-
/** "target" in mount(2) */
47-
public abstract Path getMountPoint();
48-
49-
/** "source" in mount(2) */
50-
public abstract Path getContent();
51-
}
52-
5339
private final Path linuxSandboxPath;
5440
private Path hermeticSandboxPath;
5541
private Path workingDirectory;
@@ -60,7 +46,7 @@ public static BindMount of(Path mountPoint, Path source) {
6046
private Path stderrPath;
6147
private Set<Path> writableFilesAndDirectories = ImmutableSet.of();
6248
private ImmutableSet<PathFragment> tmpfsDirectories = ImmutableSet.of();
63-
private List<BindMount> bindMounts = ImmutableList.of();
49+
private Map<Path, Path> bindMounts = ImmutableMap.of();
6450
private Path statisticsPath;
6551
private boolean useFakeHostname = false;
6652
private NetworkNamespace createNetworkNamespace = NetworkNamespace.NO_NETNS;
@@ -164,7 +150,7 @@ public LinuxSandboxCommandLineBuilder setTmpfsDirectories(
164150
* if any.
165151
*/
166152
@CanIgnoreReturnValue
167-
public LinuxSandboxCommandLineBuilder setBindMounts(List<BindMount> bindMounts) {
153+
public LinuxSandboxCommandLineBuilder setBindMounts(Map<Path, Path> bindMounts) {
168154
this.bindMounts = bindMounts;
169155
return this;
170156
}
@@ -273,11 +259,12 @@ public ImmutableList<String> buildForCommand(List<String> commandArguments) {
273259
for (PathFragment tmpfsPath : tmpfsDirectories) {
274260
commandLineBuilder.add("-e", tmpfsPath.getPathString());
275261
}
276-
for (BindMount bindMount : bindMounts) {
277-
commandLineBuilder.add("-M", bindMount.getContent().getPathString());
262+
for (Path bindMountTarget : bindMounts.keySet()) {
263+
Path bindMountSource = bindMounts.get(bindMountTarget);
264+
commandLineBuilder.add("-M", bindMountSource.getPathString());
278265
// The file is mounted in a custom location inside the sandbox.
279-
if (!bindMount.getContent().equals(bindMount.getMountPoint())) {
280-
commandLineBuilder.add("-m", bindMount.getMountPoint().getPathString());
266+
if (!bindMountSource.equals(bindMountTarget)) {
267+
commandLineBuilder.add("-m", bindMountTarget.getPathString());
281268
}
282269
}
283270
if (statisticsPath != null) {

0 commit comments

Comments
 (0)