Skip to content

Commit

Permalink
Print interactive sandboxed shell command with --sandbox_debug
Browse files Browse the repository at this point in the history
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.

PiperOrigin-RevId: 595457357
Change-Id: I6dfd410895b93fce67b9666c76c5f7757e77dc3a
  • Loading branch information
fmeum authored and copybara-github committed Jan 3, 2024
1 parent 69fa6cb commit 48ce49b
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 @@ -75,6 +75,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 @@ -259,29 +259,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 @@ -310,7 +309,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 @@ -355,7 +354,7 @@ protected SandboxedSpawn prepareSpawn(Spawn spawn, SpawnExecutionContext context
return new HardlinkedSandboxedSpawn(
sandboxPath,
sandboxExecRoot,
commandLineBuilder.build(),
commandLineBuilder.buildForCommand(spawn.getArguments()),
environment,
inputs,
outputs,
Expand All @@ -369,14 +368,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 @@ -539,4 +539,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 @@ -107,18 +107,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 @@ -97,4 +104,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 @@ -183,7 +183,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 @@ -204,7 +204,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 48ce49b

Please sign in to comment.