aboutsummaryrefslogtreecommitdiffhomepage
path: root/src/main/java/com/google/devtools/build/lib/sandbox/SandboxModule.java
diff options
context:
space:
mode:
Diffstat (limited to 'src/main/java/com/google/devtools/build/lib/sandbox/SandboxModule.java')
-rw-r--r--src/main/java/com/google/devtools/build/lib/sandbox/SandboxModule.java216
1 files changed, 180 insertions, 36 deletions
diff --git a/src/main/java/com/google/devtools/build/lib/sandbox/SandboxModule.java b/src/main/java/com/google/devtools/build/lib/sandbox/SandboxModule.java
index 14abac497b..a331f55b3d 100644
--- a/src/main/java/com/google/devtools/build/lib/sandbox/SandboxModule.java
+++ b/src/main/java/com/google/devtools/build/lib/sandbox/SandboxModule.java
@@ -17,26 +17,40 @@ package com.google.devtools.build.lib.sandbox;
import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.base.Preconditions.checkState;
+import com.google.common.base.Splitter;
import com.google.common.collect.ImmutableList;
import com.google.common.eventbus.Subscribe;
+import com.google.devtools.build.lib.actions.ExecException;
import com.google.devtools.build.lib.actions.ExecutorInitException;
+import com.google.devtools.build.lib.actions.ResourceManager;
+import com.google.devtools.build.lib.actions.Spawn;
import com.google.devtools.build.lib.actions.SpawnActionContext;
+import com.google.devtools.build.lib.actions.SpawnResult;
+import com.google.devtools.build.lib.actions.Spawns;
import com.google.devtools.build.lib.buildtool.BuildRequest;
import com.google.devtools.build.lib.buildtool.buildevent.BuildCompleteEvent;
import com.google.devtools.build.lib.buildtool.buildevent.BuildInterruptedEvent;
import com.google.devtools.build.lib.events.Event;
-import com.google.devtools.build.lib.exec.ActionContextProvider;
import com.google.devtools.build.lib.exec.ExecutorBuilder;
+import com.google.devtools.build.lib.exec.SpawnRunner;
+import com.google.devtools.build.lib.exec.apple.XcodeLocalEnvProvider;
+import com.google.devtools.build.lib.exec.local.LocalEnvProvider;
+import com.google.devtools.build.lib.exec.local.LocalExecutionOptions;
+import com.google.devtools.build.lib.exec.local.LocalSpawnRunner;
+import com.google.devtools.build.lib.exec.local.PosixLocalEnvProvider;
import com.google.devtools.build.lib.runtime.BlazeModule;
import com.google.devtools.build.lib.runtime.Command;
import com.google.devtools.build.lib.runtime.CommandEnvironment;
import com.google.devtools.build.lib.util.Fingerprint;
+import com.google.devtools.build.lib.util.OS;
import com.google.devtools.build.lib.vfs.FileSystem;
import com.google.devtools.build.lib.vfs.FileSystemUtils;
import com.google.devtools.build.lib.vfs.Path;
import com.google.devtools.build.lib.vfs.PathFragment;
import com.google.devtools.common.options.OptionsBase;
+import java.io.File;
import java.io.IOException;
+import java.time.Duration;
import javax.annotation.Nullable;
/**
@@ -96,48 +110,106 @@ public final class SandboxModule extends BlazeModule {
public void executorInit(CommandEnvironment cmdEnv, BuildRequest request, ExecutorBuilder builder)
throws ExecutorInitException {
checkNotNull(env, "env not initialized; was beforeCommand called?");
-
- SandboxOptions options = env.getOptions().getOptions(SandboxOptions.class);
- checkNotNull(options, "We were told to initialize the executor but the SandboxOptions are "
- + "not present; were they registered for all build commands?");
-
try {
- sandboxBase = computeSandboxBase(options, env);
+ setup(cmdEnv, builder);
} catch (IOException e) {
- throw new ExecutorInitException(
- "--experimental_sandbox_base points to an invalid directory", e);
+ throw new ExecutorInitException("Failed to initialize sandbox", e);
}
+ }
- ActionContextProvider provider;
- try {
- // Ensure that each build starts with a clean sandbox base directory. Otherwise using the `id`
- // that is provided by SpawnExecutionPolicy#getId to compute a base directory for a sandbox
- // might result in an already existing directory.
- if (sandboxBase.exists()) {
- FileSystemUtils.deleteTree(sandboxBase);
- }
+ private void setup(CommandEnvironment cmdEnv, ExecutorBuilder builder)
+ throws IOException {
+ SandboxOptions options = checkNotNull(env.getOptions().getOptions(SandboxOptions.class));
+ sandboxBase = computeSandboxBase(options, env);
+
+ // Ensure that each build starts with a clean sandbox base directory. Otherwise using the `id`
+ // that is provided by SpawnExecutionPolicy#getId to compute a base directory for a sandbox
+ // might result in an already existing directory.
+ if (sandboxBase.exists()) {
+ FileSystemUtils.deleteTree(sandboxBase);
+ }
- sandboxBase.createDirectoryAndParents();
- if (options.useSandboxfs) {
- Path mountPoint = sandboxBase.getRelative("sandboxfs");
- mountPoint.createDirectory();
- Path logFile = sandboxBase.getRelative("sandboxfs.log");
+ sandboxBase.createDirectoryAndParents();
+ if (options.useSandboxfs) {
+ Path mountPoint = sandboxBase.getRelative("sandboxfs");
+ mountPoint.createDirectory();
+ Path logFile = sandboxBase.getRelative("sandboxfs.log");
- env.getReporter().handle(Event.info("Mounting sandboxfs instance on " + mountPoint));
- sandboxfsProcess = RealSandboxfsProcess.mount(
- PathFragment.create(options.sandboxfsPath), mountPoint, logFile);
- provider = SandboxActionContextProvider.create(cmdEnv, sandboxBase, sandboxfsProcess);
- } else {
- provider = SandboxActionContextProvider.create(cmdEnv, sandboxBase, null);
+ env.getReporter().handle(Event.info("Mounting sandboxfs instance on " + mountPoint));
+ sandboxfsProcess = RealSandboxfsProcess.mount(
+ PathFragment.create(options.sandboxfsPath), mountPoint, logFile);
+ }
+
+ Duration timeoutKillDelay =
+ cmdEnv.getOptions().getOptions(LocalExecutionOptions.class).getLocalSigkillGraceSeconds();
+
+ boolean processWrapperSupported = ProcessWrapperSandboxedSpawnRunner.isSupported(cmdEnv);
+ // LinuxSandboxedSpawnRunner.isSupported is expensive! It runs the sandbox as a subprocess.
+ boolean linuxSandboxSupported = LinuxSandboxedSpawnRunner.isSupported(cmdEnv);
+ // DarwinSandboxedSpawnRunner.isSupported is expensive! It runs the sandbox as a subprocess.
+ boolean darwinSandboxSupported = DarwinSandboxedSpawnRunner.isSupported(cmdEnv);
+
+ // This works on most platforms, but isn't the best choice, so we put it first and let later
+ // platform-specific sandboxing strategies become the default.
+ if (processWrapperSupported) {
+ SpawnRunner spawnRunner =
+ withFallback(
+ cmdEnv,
+ new ProcessWrapperSandboxedSpawnRunner(
+ cmdEnv, sandboxBase, cmdEnv.getRuntime().getProductName(), timeoutKillDelay));
+ builder.addActionContext(
+ new ProcessWrapperSandboxedStrategy(cmdEnv.getExecRoot(), spawnRunner));
+ }
+
+ if (options.enableDockerSandbox) {
+ // This strategy uses Docker to execute spawns. It should work on all platforms that support
+ // Docker.
+ Path pathToDocker = getPathToDockerClient(cmdEnv);
+ // DockerSandboxedSpawnRunner.isSupported is expensive! It runs docker as a subprocess, and
+ // docker hangs sometimes.
+ if (pathToDocker != null && DockerSandboxedSpawnRunner.isSupported(cmdEnv, pathToDocker)) {
+ String defaultImage = options.dockerImage;
+ boolean useCustomizedImages = options.dockerUseCustomizedImages;
+ SpawnRunner spawnRunner =
+ withFallback(
+ cmdEnv,
+ new DockerSandboxedSpawnRunner(
+ cmdEnv,
+ pathToDocker,
+ sandboxBase,
+ defaultImage,
+ timeoutKillDelay,
+ useCustomizedImages));
+ builder.addActionContext(
+ new DockerSandboxedStrategy(cmdEnv.getExecRoot(), spawnRunner));
}
- } catch (IOException e) {
- throw new ExecutorInitException("Failed to initialize sandbox", e);
+ } else if (options.dockerVerbose) {
+ cmdEnv.getReporter().handle(Event.info(
+ "Docker sandboxing disabled. Use the '--experimental_enable_docker_sandbox' command "
+ + "line option to enable it"));
+ }
+
+ // This is the preferred sandboxing strategy on Linux.
+ if (linuxSandboxSupported) {
+ SpawnRunner spawnRunner =
+ withFallback(
+ cmdEnv,
+ LinuxSandboxedStrategy.create(
+ cmdEnv, sandboxBase, timeoutKillDelay, sandboxfsProcess));
+ builder.addActionContext(new LinuxSandboxedStrategy(cmdEnv.getExecRoot(), spawnRunner));
}
- builder.addActionContextProvider(provider);
- if (LinuxSandboxedSpawnRunner.isSupported(cmdEnv)
- || DarwinSandboxedSpawnRunner.isSupported(cmdEnv)
- || ProcessWrapperSandboxedSpawnRunner.isSupported(cmdEnv)) {
+ // This is the preferred sandboxing strategy on macOS.
+ if (darwinSandboxSupported) {
+ SpawnRunner spawnRunner =
+ withFallback(
+ cmdEnv,
+ new DarwinSandboxedSpawnRunner(
+ cmdEnv, sandboxBase, timeoutKillDelay, sandboxfsProcess));
+ builder.addActionContext(new DarwinSandboxedStrategy(cmdEnv.getExecRoot(), spawnRunner));
+ }
+
+ if (processWrapperSupported || linuxSandboxSupported || darwinSandboxSupported) {
// This makes the "sandboxed" strategy available via --spawn_strategy=sandboxed,
// but it is not necessarily the default.
builder.addStrategyByContext(SpawnActionContext.class, "sandboxed");
@@ -152,6 +224,78 @@ public final class SandboxModule extends BlazeModule {
shouldCleanupSandboxBase = !options.sandboxDebug;
}
+ private static Path getPathToDockerClient(CommandEnvironment cmdEnv) {
+ String path = cmdEnv.getClientEnv().getOrDefault("PATH", "");
+
+ // TODO(philwo): Does this return the correct result if one of the elements intentionally ends
+ // in white space?
+ Splitter pathSplitter =
+ Splitter.on(OS.getCurrent() == OS.WINDOWS ? ';' : ':').trimResults().omitEmptyStrings();
+
+ FileSystem fs = cmdEnv.getRuntime().getFileSystem();
+
+ for (String pathElement : pathSplitter.split(path)) {
+ // Sometimes the PATH contains the non-absolute entry "." - this resolves it against the
+ // current working directory.
+ pathElement = new File(pathElement).getAbsolutePath();
+ try {
+ for (Path dentry : fs.getPath(pathElement).getDirectoryEntries()) {
+ if (dentry.getBaseName().replace(".exe", "").equals("docker")) {
+ return dentry;
+ }
+ }
+ } catch (IOException e) {
+ continue;
+ }
+ }
+
+ return null;
+ }
+
+ private static SpawnRunner withFallback(CommandEnvironment env, SpawnRunner sandboxSpawnRunner) {
+ return new SandboxFallbackSpawnRunner(sandboxSpawnRunner, createFallbackRunner(env));
+ }
+
+ private static SpawnRunner createFallbackRunner(CommandEnvironment env) {
+ LocalExecutionOptions localExecutionOptions =
+ env.getOptions().getOptions(LocalExecutionOptions.class);
+ LocalEnvProvider localEnvProvider =
+ OS.getCurrent() == OS.DARWIN
+ ? new XcodeLocalEnvProvider(env.getClientEnv())
+ : new PosixLocalEnvProvider(env.getClientEnv());
+ return
+ new LocalSpawnRunner(
+ env.getExecRoot(),
+ localExecutionOptions,
+ ResourceManager.instance(),
+ localEnvProvider);
+ }
+
+ private static final class SandboxFallbackSpawnRunner implements SpawnRunner {
+ private final SpawnRunner sandboxSpawnRunner;
+ private final SpawnRunner fallbackSpawnRunner;
+
+ SandboxFallbackSpawnRunner(SpawnRunner sandboxSpawnRunner, SpawnRunner fallbackSpawnRunner) {
+ this.sandboxSpawnRunner = sandboxSpawnRunner;
+ this.fallbackSpawnRunner = fallbackSpawnRunner;
+ }
+
+ @Override
+ public String getName() {
+ return "sandbox-fallback";
+ }
+
+ @Override
+ public SpawnResult exec(Spawn spawn, SpawnExecutionContext context)
+ throws InterruptedException, IOException, ExecException {
+ if (!Spawns.mayBeSandboxed(spawn)) {
+ return fallbackSpawnRunner.exec(spawn, context);
+ } else {
+ return sandboxSpawnRunner.exec(spawn, context);
+ }
+ }
+ }
+
private void unmountSandboxfs(String reason) {
if (sandboxfsProcess != null) {
checkNotNull(env, "env not initialized; was beforeCommand called?");
@@ -163,12 +307,12 @@ public final class SandboxModule extends BlazeModule {
}
@Subscribe
- public void buildComplete(BuildCompleteEvent event) {
+ public void buildComplete(@SuppressWarnings("unused") BuildCompleteEvent event) {
unmountSandboxfs("Build complete; unmounting sandboxfs...");
}
@Subscribe
- public void buildInterrupted(BuildInterruptedEvent event) {
+ public void buildInterrupted(@SuppressWarnings("unused") BuildInterruptedEvent event) {
unmountSandboxfs("Build interrupted; unmounting sandboxfs...");
}