Skip to content

Commit

Permalink
[7.0.1] Print interactive sandboxed shell command with `--sandbox_deb…
Browse files Browse the repository at this point in the history
…ug` (#20734)

The `--sandbox_debug` output for the Linux sandbox now also contains a
copyable command that drops the user into an interactive shell in an
identical sandboxed environment. This is in particular meant to address
the increased complexity of the bind mount structure incurred by the
flip of `--incompatible_sandbox_hermetic_tmp`.

Closes #20708.

Commit
48ce49b

PiperOrigin-RevId: 595457357
Change-Id: I6dfd410895b93fce67b9666c76c5f7757e77dc3a

Co-authored-by: Fabian Meumertzheim <[email protected]>
  • Loading branch information
bazel-io and fmeum authored Jan 3, 2024
1 parent 7a42ff3 commit e86f9b5
Show file tree
Hide file tree
Showing 13 changed files with 120 additions and 70 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
package com.google.devtools.build.lib.sandbox;

import static java.nio.charset.StandardCharsets.UTF_8;
import static java.util.stream.Collectors.joining;

import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableList;
Expand Down Expand Up @@ -59,7 +60,8 @@
import java.time.Duration;
import java.time.Instant;
import java.util.Map;
import javax.annotation.Nullable;
import java.util.Optional;
import java.util.stream.Stream;

/** Abstract common ancestor for sandbox spawn runners implementing the common parts. */
abstract class AbstractSandboxSpawnRunner implements SpawnRunner {
Expand Down Expand Up @@ -224,7 +226,7 @@ private final SpawnResult run(
StringBuilder msg = new StringBuilder("Action failed to execute: java.io.IOException: ");
msg.append(exceptionMsg);
msg.append("\n");
if (sandboxDebugOutput != null) {
if (!sandboxDebugOutput.isEmpty()) {
msg.append("Sandbox debug output:\n");
msg.append(sandboxDebugOutput);
msg.append("\n");
Expand Down Expand Up @@ -294,12 +296,12 @@ private final SpawnResult run(
}

String sandboxDebugOutput = getSandboxDebugOutput(sandbox);
if (sandboxDebugOutput != null) {
if (!sandboxDebugOutput.isEmpty()) {
reporter.handle(
Event.of(
EventKind.DEBUG,
String.format(
"Sandbox debug output for %s %s: %s",
"Sandbox debug output for %s %s:\n%s",
originalSpawn.getMnemonic(),
originalSpawn.getTargetLabel(),
sandboxDebugOutput)));
Expand Down Expand Up @@ -334,18 +336,21 @@ private final SpawnResult run(
return spawnResultBuilder.build();
}

@Nullable
private String getSandboxDebugOutput(SandboxedSpawn sandbox) throws IOException {
Optional<String> sandboxDebugOutput = Optional.empty();
Path sandboxDebugPath = sandbox.getSandboxDebugPath();
if (sandboxDebugPath != null && sandboxDebugPath.exists()) {
try (InputStream inputStream = sandboxDebugPath.getInputStream()) {
String msg = new String(inputStream.readAllBytes(), UTF_8);
if (!msg.isEmpty()) {
return msg;
sandboxDebugOutput = Optional.of(msg);
}
}
}
return null;
Optional<String> interactiveDebugInstructions = sandbox.getInteractiveDebugInstructions();
return Stream.of(sandboxDebugOutput, interactiveDebugInstructions)
.flatMap(Optional::stream)
.collect(joining("\n"));
}

private boolean wasTimeout(Duration timeout, Duration wallTime) {
Expand Down
1 change: 1 addition & 0 deletions src/main/java/com/google/devtools/build/lib/sandbox/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ java_library(
":sandbox_helpers",
":sandbox_options",
"//src/main/java/com/google/devtools/build/lib/exec:tree_deleter",
"//src/main/java/com/google/devtools/build/lib/util:command",
"//src/main/java/com/google/devtools/build/lib/util:describable_execution_unit",
"//src/main/java/com/google/devtools/build/lib/vfs",
"//src/main/java/com/google/devtools/build/lib/vfs:pathfragment",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -258,29 +258,30 @@ protected SandboxedSpawn prepareSpawn(Spawn spawn, SpawnExecutionContext context
allowNetwork
|| Spawns.requiresNetwork(spawn, getSandboxOptions().defaultSandboxAllowNetwork);

return new SymlinkedSandboxedSpawn(
sandboxPath,
sandboxExecRoot,
commandLine,
environment,
inputs,
outputs,
writableDirs,
treeDeleter,
/* sandboxDebugPath= */ null,
statisticsPath,
spawn.getMnemonic()) {
@Override
public void createFileSystem() throws IOException, InterruptedException {
super.createFileSystem();
writeConfig(
sandboxConfigPath,
writableDirs,
getInaccessiblePaths(),
allowNetworkForThisSpawn,
statisticsPath);
}
};
return new SymlinkedSandboxedSpawn(
sandboxPath,
sandboxExecRoot,
commandLine,
environment,
inputs,
outputs,
writableDirs,
treeDeleter,
/* sandboxDebugPath= */ null,
statisticsPath,
/* interactiveDebugArguments= */ null,
spawn.getMnemonic()) {
@Override
public void createFileSystem() throws IOException, InterruptedException {
super.createFileSystem();
writeConfig(
sandboxConfigPath,
writableDirs,
getInaccessiblePaths(),
allowNetworkForThisSpawn,
statisticsPath);
}
};
}

private void writeConfig(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,6 @@ public static BindMount of(Path mountPoint, Path source) {
}

private final Path linuxSandboxPath;
private final List<String> commandArguments;
private Path hermeticSandboxPath;
private Path workingDirectory;
private Duration timeout;
Expand All @@ -72,15 +71,13 @@ public static BindMount of(Path mountPoint, Path source) {
private boolean sigintSendsSigterm = false;
private String cgroupsDir;

private LinuxSandboxCommandLineBuilder(Path linuxSandboxPath, List<String> commandArguments) {
private LinuxSandboxCommandLineBuilder(Path linuxSandboxPath) {
this.linuxSandboxPath = linuxSandboxPath;
this.commandArguments = commandArguments;
}

/** Returns a new command line builder for the {@code linux-sandbox} tool. */
public static LinuxSandboxCommandLineBuilder commandLineBuilder(
Path linuxSandboxPath, List<String> commandArguments) {
return new LinuxSandboxCommandLineBuilder(linuxSandboxPath, commandArguments);
public static LinuxSandboxCommandLineBuilder commandLineBuilder(Path linuxSandboxPath) {
return new LinuxSandboxCommandLineBuilder(linuxSandboxPath);
}

/**
Expand Down Expand Up @@ -247,7 +244,7 @@ public LinuxSandboxCommandLineBuilder addExecutionInfo(Map<String, String> execu
}

/** Builds the command line to invoke a specific command using the {@code linux-sandbox} tool. */
public ImmutableList<String> build() {
public ImmutableList<String> buildForCommand(List<String> commandArguments) {
Preconditions.checkState(
!(this.useFakeUsername && this.useFakeRoot),
"useFakeUsername and useFakeRoot are exclusive");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -106,10 +106,9 @@ private static boolean computeIsSupported(CommandEnvironment cmdEnv, Path linuxS
throws InterruptedException {
LocalExecutionOptions options = cmdEnv.getOptions().getOptions(LocalExecutionOptions.class);
ImmutableList<String> linuxSandboxArgv =
LinuxSandboxCommandLineBuilder.commandLineBuilder(
linuxSandbox, ImmutableList.of("/bin/true"))
LinuxSandboxCommandLineBuilder.commandLineBuilder(linuxSandbox)
.setTimeout(options.getLocalSigkillGraceSeconds())
.build();
.buildForCommand(ImmutableList.of("/bin/true"));
ImmutableMap<String, String> env = ImmutableMap.of();
Path execRoot = cmdEnv.getExecRoot();
File cwd = execRoot.getPathFile();
Expand Down Expand Up @@ -309,7 +308,7 @@ protected SandboxedSpawn prepareSpawn(Spawn spawn, SpawnExecutionContext context
boolean createNetworkNamespace =
!(allowNetwork || Spawns.requiresNetwork(spawn, sandboxOptions.defaultSandboxAllowNetwork));
LinuxSandboxCommandLineBuilder commandLineBuilder =
LinuxSandboxCommandLineBuilder.commandLineBuilder(linuxSandbox, spawn.getArguments())
LinuxSandboxCommandLineBuilder.commandLineBuilder(linuxSandbox)
.addExecutionInfo(spawn.getExecutionInfo())
.setWritableFilesAndDirectories(writableDirs)
.setTmpfsDirectories(ImmutableSet.copyOf(getSandboxOptions().sandboxTmpfsPath))
Expand Down Expand Up @@ -354,7 +353,7 @@ protected SandboxedSpawn prepareSpawn(Spawn spawn, SpawnExecutionContext context
return new HardlinkedSandboxedSpawn(
sandboxPath,
sandboxExecRoot,
commandLineBuilder.build(),
commandLineBuilder.buildForCommand(spawn.getArguments()),
environment,
inputs,
outputs,
Expand All @@ -368,14 +367,15 @@ protected SandboxedSpawn prepareSpawn(Spawn spawn, SpawnExecutionContext context
return new SymlinkedSandboxedSpawn(
sandboxPath,
sandboxExecRoot,
commandLineBuilder.build(),
commandLineBuilder.buildForCommand(spawn.getArguments()),
environment,
inputs,
outputs,
writableDirs,
treeDeleter,
sandboxDebugPath,
statisticsPath,
makeInteractiveDebugArguments(commandLineBuilder, sandboxOptions),
spawn.getMnemonic());
}
}
Expand Down Expand Up @@ -538,4 +538,13 @@ public void cleanupSandboxBase(Path sandboxBase, TreeDeleter treeDeleter) throws

super.cleanupSandboxBase(sandboxBase, treeDeleter);
}

@Nullable
private ImmutableList<String> makeInteractiveDebugArguments(
LinuxSandboxCommandLineBuilder commandLineBuilder, SandboxOptions sandboxOptions) {
if (!sandboxOptions.sandboxDebug) {
return null;
}
return commandLineBuilder.buildForCommand(ImmutableList.of("/bin/sh", "-i"));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -106,18 +106,19 @@ protected SandboxedSpawn prepareSpawn(Spawn spawn, SpawnExecutionContext context
null);
SandboxOutputs outputs = helpers.getOutputs(spawn);

return new SymlinkedSandboxedSpawn(
sandboxPath,
sandboxExecRoot,
commandLineBuilder.build(),
environment,
inputs,
outputs,
getWritableDirs(sandboxExecRoot, sandboxExecRoot, environment),
treeDeleter,
/* sandboxDebugPath= */ null,
statisticsPath,
spawn.getMnemonic());
return new SymlinkedSandboxedSpawn(
sandboxPath,
sandboxExecRoot,
commandLineBuilder.build(),
environment,
inputs,
outputs,
getWritableDirs(sandboxExecRoot, sandboxExecRoot, environment),
treeDeleter,
/* sandboxDebugPath= */ null,
statisticsPath,
/* interactiveDebugArguments= */ null,
spawn.getMnemonic());
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
import com.google.devtools.build.lib.util.DescribableExecutionUnit;
import com.google.devtools.build.lib.vfs.Path;
import java.io.IOException;
import java.util.Optional;
import javax.annotation.Nullable;

/**
Expand Down Expand Up @@ -61,4 +62,12 @@ default boolean useSubprocessTimeout() {

/** Deletes the sandbox directory. */
void delete();

/**
* Returns user-facing instructions for starting an interactive sandboxed environment identical to
* the one in which this spawn is executed.
*/
default Optional<String> getInteractiveDebugInstructions() {
return Optional.empty();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,13 @@
import com.google.devtools.build.lib.exec.TreeDeleter;
import com.google.devtools.build.lib.sandbox.SandboxHelpers.SandboxInputs;
import com.google.devtools.build.lib.sandbox.SandboxHelpers.SandboxOutputs;
import com.google.devtools.build.lib.util.CommandDescriptionForm;
import com.google.devtools.build.lib.util.CommandFailureUtils;
import com.google.devtools.build.lib.vfs.Path;
import com.google.devtools.build.lib.vfs.PathFragment;
import java.io.IOException;
import java.util.LinkedHashSet;
import java.util.Optional;
import java.util.Set;
import javax.annotation.Nullable;

Expand All @@ -37,6 +40,8 @@ public class SymlinkedSandboxedSpawn extends AbstractContainerizingSandboxedSpaw
/** Mnemonic of the action running in this spawn. */
private final String mnemonic;

@Nullable private final ImmutableList<String> interactiveDebugArguments;

public SymlinkedSandboxedSpawn(
Path sandboxPath,
Path sandboxExecRoot,
Expand All @@ -48,6 +53,7 @@ public SymlinkedSandboxedSpawn(
TreeDeleter treeDeleter,
@Nullable Path sandboxDebugPath,
@Nullable Path statisticsPath,
@Nullable ImmutableList<String> interactiveDebugArguments,
String mnemonic) {
super(
sandboxPath,
Expand All @@ -62,6 +68,7 @@ public SymlinkedSandboxedSpawn(
statisticsPath,
mnemonic);
this.mnemonic = isNullOrEmpty(mnemonic) ? "_NoMnemonic_" : mnemonic;
this.interactiveDebugArguments = interactiveDebugArguments;
}

@Override
Expand Down Expand Up @@ -96,4 +103,23 @@ public void delete() {
SandboxStash.stashSandbox(sandboxPath, mnemonic);
super.delete();
}

@Nullable
@Override
public Optional<String> getInteractiveDebugInstructions() {
if (interactiveDebugArguments == null) {
return Optional.empty();
}
return Optional.of(
"Run this command to start an interactive shell in an identical sandboxed environment:\n"
+ CommandFailureUtils.describeCommand(
CommandDescriptionForm.COMPLETE,
/* prettyPrintArgs= */ false,
interactiveDebugArguments,
getEnvironment(),
/* environmentVariablesToClear= */ null,
/* cwd= */ null,
/* configurationChecksum= */ null,
/* executionPlatformLabel= */ null));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -181,7 +181,7 @@ protected Subprocess createProcess() throws IOException, UserExecException {
// Mostly tests require network, and some blaze run commands, but no workers.
LinuxSandboxCommandLineBuilder commandLineBuilder =
LinuxSandboxCommandLineBuilder.commandLineBuilder(
this.hardenedSandboxOptions.sandboxBinary(), args)
this.hardenedSandboxOptions.sandboxBinary())
.setWritableFilesAndDirectories(getWritableDirs(workDir))
.setTmpfsDirectories(ImmutableSet.copyOf(this.hardenedSandboxOptions.tmpfsPath()))
.setPersistentProcess(true)
Expand All @@ -202,7 +202,7 @@ protected Subprocess createProcess() throws IOException, UserExecException {
commandLineBuilder.setUseFakeUsername(true);
}

args = commandLineBuilder.build();
args = commandLineBuilder.buildForCommand(args);
}
return createProcessBuilder(args).start();
}
Expand Down
Loading

0 comments on commit e86f9b5

Please sign in to comment.