diff --git a/src/main/java/com/google/devtools/build/lib/sandbox/AbstractSandboxSpawnRunner.java b/src/main/java/com/google/devtools/build/lib/sandbox/AbstractSandboxSpawnRunner.java index 2a0fa882d93725..61868714abc722 100644 --- a/src/main/java/com/google/devtools/build/lib/sandbox/AbstractSandboxSpawnRunner.java +++ b/src/main/java/com/google/devtools/build/lib/sandbox/AbstractSandboxSpawnRunner.java @@ -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; @@ -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 { @@ -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"); @@ -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))); @@ -334,18 +336,21 @@ private final SpawnResult run( return spawnResultBuilder.build(); } - @Nullable private String getSandboxDebugOutput(SandboxedSpawn sandbox) throws IOException { + Optional 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 interactiveDebugInstructions = sandbox.getInteractiveDebugInstructions(); + return Stream.of(sandboxDebugOutput, interactiveDebugInstructions) + .flatMap(Optional::stream) + .collect(joining("\n")); } private boolean wasTimeout(Duration timeout, Duration wallTime) { diff --git a/src/main/java/com/google/devtools/build/lib/sandbox/BUILD b/src/main/java/com/google/devtools/build/lib/sandbox/BUILD index 87ac37c3644eb0..091dc914276592 100644 --- a/src/main/java/com/google/devtools/build/lib/sandbox/BUILD +++ b/src/main/java/com/google/devtools/build/lib/sandbox/BUILD @@ -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", diff --git a/src/main/java/com/google/devtools/build/lib/sandbox/DarwinSandboxedSpawnRunner.java b/src/main/java/com/google/devtools/build/lib/sandbox/DarwinSandboxedSpawnRunner.java index fad5cfa108b11e..40cd3499675d27 100644 --- a/src/main/java/com/google/devtools/build/lib/sandbox/DarwinSandboxedSpawnRunner.java +++ b/src/main/java/com/google/devtools/build/lib/sandbox/DarwinSandboxedSpawnRunner.java @@ -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( diff --git a/src/main/java/com/google/devtools/build/lib/sandbox/LinuxSandboxCommandLineBuilder.java b/src/main/java/com/google/devtools/build/lib/sandbox/LinuxSandboxCommandLineBuilder.java index cf80265a85e6f0..967c08b5d6ce59 100644 --- a/src/main/java/com/google/devtools/build/lib/sandbox/LinuxSandboxCommandLineBuilder.java +++ b/src/main/java/com/google/devtools/build/lib/sandbox/LinuxSandboxCommandLineBuilder.java @@ -51,7 +51,6 @@ public static BindMount of(Path mountPoint, Path source) { } private final Path linuxSandboxPath; - private final List commandArguments; private Path hermeticSandboxPath; private Path workingDirectory; private Duration timeout; @@ -72,15 +71,13 @@ public static BindMount of(Path mountPoint, Path source) { private boolean sigintSendsSigterm = false; private String cgroupsDir; - private LinuxSandboxCommandLineBuilder(Path linuxSandboxPath, List 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 commandArguments) { - return new LinuxSandboxCommandLineBuilder(linuxSandboxPath, commandArguments); + public static LinuxSandboxCommandLineBuilder commandLineBuilder(Path linuxSandboxPath) { + return new LinuxSandboxCommandLineBuilder(linuxSandboxPath); } /** @@ -247,7 +244,7 @@ public LinuxSandboxCommandLineBuilder addExecutionInfo(Map execu } /** Builds the command line to invoke a specific command using the {@code linux-sandbox} tool. */ - public ImmutableList build() { + public ImmutableList buildForCommand(List commandArguments) { Preconditions.checkState( !(this.useFakeUsername && this.useFakeRoot), "useFakeUsername and useFakeRoot are exclusive"); diff --git a/src/main/java/com/google/devtools/build/lib/sandbox/LinuxSandboxedSpawnRunner.java b/src/main/java/com/google/devtools/build/lib/sandbox/LinuxSandboxedSpawnRunner.java index 2d593e7a28003e..df6f5fbeaed26a 100644 --- a/src/main/java/com/google/devtools/build/lib/sandbox/LinuxSandboxedSpawnRunner.java +++ b/src/main/java/com/google/devtools/build/lib/sandbox/LinuxSandboxedSpawnRunner.java @@ -106,10 +106,9 @@ private static boolean computeIsSupported(CommandEnvironment cmdEnv, Path linuxS throws InterruptedException { LocalExecutionOptions options = cmdEnv.getOptions().getOptions(LocalExecutionOptions.class); ImmutableList linuxSandboxArgv = - LinuxSandboxCommandLineBuilder.commandLineBuilder( - linuxSandbox, ImmutableList.of("/bin/true")) + LinuxSandboxCommandLineBuilder.commandLineBuilder(linuxSandbox) .setTimeout(options.getLocalSigkillGraceSeconds()) - .build(); + .buildForCommand(ImmutableList.of("/bin/true")); ImmutableMap env = ImmutableMap.of(); Path execRoot = cmdEnv.getExecRoot(); File cwd = execRoot.getPathFile(); @@ -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)) @@ -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, @@ -368,7 +367,7 @@ protected SandboxedSpawn prepareSpawn(Spawn spawn, SpawnExecutionContext context return new SymlinkedSandboxedSpawn( sandboxPath, sandboxExecRoot, - commandLineBuilder.build(), + commandLineBuilder.buildForCommand(spawn.getArguments()), environment, inputs, outputs, @@ -376,6 +375,7 @@ protected SandboxedSpawn prepareSpawn(Spawn spawn, SpawnExecutionContext context treeDeleter, sandboxDebugPath, statisticsPath, + makeInteractiveDebugArguments(commandLineBuilder, sandboxOptions), spawn.getMnemonic()); } } @@ -538,4 +538,13 @@ public void cleanupSandboxBase(Path sandboxBase, TreeDeleter treeDeleter) throws super.cleanupSandboxBase(sandboxBase, treeDeleter); } + + @Nullable + private ImmutableList makeInteractiveDebugArguments( + LinuxSandboxCommandLineBuilder commandLineBuilder, SandboxOptions sandboxOptions) { + if (!sandboxOptions.sandboxDebug) { + return null; + } + return commandLineBuilder.buildForCommand(ImmutableList.of("/bin/sh", "-i")); + } } diff --git a/src/main/java/com/google/devtools/build/lib/sandbox/ProcessWrapperSandboxedSpawnRunner.java b/src/main/java/com/google/devtools/build/lib/sandbox/ProcessWrapperSandboxedSpawnRunner.java index 2f7608dcc8b857..a01a4e6da25131 100644 --- a/src/main/java/com/google/devtools/build/lib/sandbox/ProcessWrapperSandboxedSpawnRunner.java +++ b/src/main/java/com/google/devtools/build/lib/sandbox/ProcessWrapperSandboxedSpawnRunner.java @@ -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 diff --git a/src/main/java/com/google/devtools/build/lib/sandbox/SandboxedSpawn.java b/src/main/java/com/google/devtools/build/lib/sandbox/SandboxedSpawn.java index 1a355bc90b6733..44657780bbb446 100644 --- a/src/main/java/com/google/devtools/build/lib/sandbox/SandboxedSpawn.java +++ b/src/main/java/com/google/devtools/build/lib/sandbox/SandboxedSpawn.java @@ -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; /** @@ -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 getInteractiveDebugInstructions() { + return Optional.empty(); + } } diff --git a/src/main/java/com/google/devtools/build/lib/sandbox/SymlinkedSandboxedSpawn.java b/src/main/java/com/google/devtools/build/lib/sandbox/SymlinkedSandboxedSpawn.java index 57a09356f92cb2..4462d8a17df739 100644 --- a/src/main/java/com/google/devtools/build/lib/sandbox/SymlinkedSandboxedSpawn.java +++ b/src/main/java/com/google/devtools/build/lib/sandbox/SymlinkedSandboxedSpawn.java @@ -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; @@ -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 interactiveDebugArguments; + public SymlinkedSandboxedSpawn( Path sandboxPath, Path sandboxExecRoot, @@ -48,6 +53,7 @@ public SymlinkedSandboxedSpawn( TreeDeleter treeDeleter, @Nullable Path sandboxDebugPath, @Nullable Path statisticsPath, + @Nullable ImmutableList interactiveDebugArguments, String mnemonic) { super( sandboxPath, @@ -62,6 +68,7 @@ public SymlinkedSandboxedSpawn( statisticsPath, mnemonic); this.mnemonic = isNullOrEmpty(mnemonic) ? "_NoMnemonic_" : mnemonic; + this.interactiveDebugArguments = interactiveDebugArguments; } @Override @@ -96,4 +103,23 @@ public void delete() { SandboxStash.stashSandbox(sandboxPath, mnemonic); super.delete(); } + + @Nullable + @Override + public Optional 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)); + } } diff --git a/src/main/java/com/google/devtools/build/lib/worker/SandboxedWorker.java b/src/main/java/com/google/devtools/build/lib/worker/SandboxedWorker.java index 9d0509181a8b53..06453e46c6a233 100644 --- a/src/main/java/com/google/devtools/build/lib/worker/SandboxedWorker.java +++ b/src/main/java/com/google/devtools/build/lib/worker/SandboxedWorker.java @@ -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) @@ -202,7 +202,7 @@ protected Subprocess createProcess() throws IOException, UserExecException { commandLineBuilder.setUseFakeUsername(true); } - args = commandLineBuilder.build(); + args = commandLineBuilder.buildForCommand(args); } return createProcessBuilder(args).start(); } diff --git a/src/test/java/com/google/devtools/build/lib/sandbox/LinuxSandboxCommandLineBuilderTest.java b/src/test/java/com/google/devtools/build/lib/sandbox/LinuxSandboxCommandLineBuilderTest.java index a8caa07f8836d2..9f8df6d9c30997 100644 --- a/src/test/java/com/google/devtools/build/lib/sandbox/LinuxSandboxCommandLineBuilderTest.java +++ b/src/test/java/com/google/devtools/build/lib/sandbox/LinuxSandboxCommandLineBuilderTest.java @@ -53,11 +53,10 @@ public void testLinuxSandboxCommandLineBuilder_fakeRootAndFakeUsernameAreExclusi assertThrows( IllegalStateException.class, () -> - LinuxSandboxCommandLineBuilder.commandLineBuilder( - linuxSandboxPath, commandArguments) + LinuxSandboxCommandLineBuilder.commandLineBuilder(linuxSandboxPath) .setUseFakeRoot(true) .setUseFakeUsername(true) - .build()); + .buildForCommand(commandArguments)); assertThat(e).hasMessageThat().contains("exclusive"); } @@ -75,8 +74,8 @@ public void testLinuxSandboxCommandLineBuilder_buildsWithoutOptionalArguments() .build(); List commandLine = - LinuxSandboxCommandLineBuilder.commandLineBuilder(linuxSandboxPath, commandArguments) - .build(); + LinuxSandboxCommandLineBuilder.commandLineBuilder(linuxSandboxPath) + .buildForCommand(commandArguments); assertThat(commandLine).containsExactlyElementsIn(expectedCommandLine).inOrder(); } @@ -163,7 +162,7 @@ public void testLinuxSandboxCommandLineBuilder_buildsWithOptionalArguments() { .build(); List commandLine = - LinuxSandboxCommandLineBuilder.commandLineBuilder(linuxSandboxPath, commandArguments) + LinuxSandboxCommandLineBuilder.commandLineBuilder(linuxSandboxPath) .setWorkingDirectory(workingDirectory) .setStdoutPath(stdoutPath) .setStderrPath(stderrPath) @@ -180,7 +179,7 @@ public void testLinuxSandboxCommandLineBuilder_buildsWithOptionalArguments() { .setSandboxDebugPath(sandboxDebugPath.getPathString()) .setPersistentProcess(true) .setCgroupsDir(cgroupsDir) - .build(); + .buildForCommand(commandArguments); assertThat(commandLine).containsExactlyElementsIn(expectedCommandLine).inOrder(); } diff --git a/src/test/java/com/google/devtools/build/lib/sandbox/SymlinkedSandboxedSpawnTest.java b/src/test/java/com/google/devtools/build/lib/sandbox/SymlinkedSandboxedSpawnTest.java index 1dd8e66f6640a9..dfc659bcb6d3ec 100644 --- a/src/test/java/com/google/devtools/build/lib/sandbox/SymlinkedSandboxedSpawnTest.java +++ b/src/test/java/com/google/devtools/build/lib/sandbox/SymlinkedSandboxedSpawnTest.java @@ -83,6 +83,7 @@ public void createFileSystem() throws Exception { new SynchronousTreeDeleter(), /* sandboxDebugPath= */ null, /* statisticsPath= */ null, + /* interactiveDebugArguments= */ null, "SomeMnemonic"); symlinkedExecRoot.createFileSystem(); @@ -114,6 +115,7 @@ public void copyOutputs() throws Exception { new SynchronousTreeDeleter(), /* sandboxDebugPath= */ null, /* statisticsPath= */ null, + /* interactiveDebugArguments= */ null, "SomeMnemonic"); symlinkedExecRoot.createFileSystem(); diff --git a/src/test/java/com/google/devtools/build/lib/shell/CommandUsingLinuxSandboxTest.java b/src/test/java/com/google/devtools/build/lib/shell/CommandUsingLinuxSandboxTest.java index 0a36c62c3d615f..f4278c5e6406e5 100644 --- a/src/test/java/com/google/devtools/build/lib/shell/CommandUsingLinuxSandboxTest.java +++ b/src/test/java/com/google/devtools/build/lib/shell/CommandUsingLinuxSandboxTest.java @@ -76,8 +76,8 @@ public void testLinuxSandboxedCommand_echo() throws Exception { ImmutableList commandArguments = ImmutableList.of("echo", "sleep furiously"); List fullCommandLine = - LinuxSandboxCommandLineBuilder.commandLineBuilder(getLinuxSandboxPath(), commandArguments) - .build(); + LinuxSandboxCommandLineBuilder.commandLineBuilder(getLinuxSandboxPath()) + .buildForCommand(commandArguments); Command command = new Command(fullCommandLine.toArray(new String[0])); CommandResult commandResult = command.execute(); @@ -98,9 +98,9 @@ private void checkLinuxSandboxStatistics(Duration userTimeToSpend, Duration syst Path statisticsFilePath = outputDir.getRelative("stats.out"); List fullCommandLine = - LinuxSandboxCommandLineBuilder.commandLineBuilder(getLinuxSandboxPath(), commandArguments) + LinuxSandboxCommandLineBuilder.commandLineBuilder(getLinuxSandboxPath()) .setStatisticsPath(statisticsFilePath) - .build(); + .buildForCommand(commandArguments); ExecutionStatisticsTestUtil.executeCommandAndCheckStatisticsAboutCpuTimeSpent( userTimeToSpend, systemTimeToSpend, fullCommandLine, statisticsFilePath); diff --git a/src/tools/remote/src/main/java/com/google/devtools/build/remote/worker/RemoteWorker.java b/src/tools/remote/src/main/java/com/google/devtools/build/remote/worker/RemoteWorker.java index 54955ff5fdc6a6..7336879f246fe0 100644 --- a/src/tools/remote/src/main/java/com/google/devtools/build/remote/worker/RemoteWorker.java +++ b/src/tools/remote/src/main/java/com/google/devtools/build/remote/worker/RemoteWorker.java @@ -383,8 +383,8 @@ private static Path prepareSandboxRunner(FileSystem fs, RemoteWorkerOptions remo CommandResult cmdResult = null; Command cmd = new Command( - LinuxSandboxCommandLineBuilder.commandLineBuilder(sandboxPath, ImmutableList.of("true")) - .build() + LinuxSandboxCommandLineBuilder.commandLineBuilder(sandboxPath) + .buildForCommand(ImmutableList.of("true")) .toArray(new String[0]), ImmutableMap.of(), sandboxPath.getParentDirectory().getPathFile());