aboutsummaryrefslogtreecommitdiffhomepage
path: root/src/main/java/com/google/devtools/build/lib
diff options
context:
space:
mode:
authorGravatar ulfjack <ulfjack@google.com>2017-07-12 12:10:11 +0200
committerGravatar László Csomor <laszlocsomor@google.com>2017-07-12 16:02:39 +0200
commit264f40fb42ce1390f9ee420f923b77ef3a82327f (patch)
tree11e16746189e6ee28b57c9294f8570715e4edb87 /src/main/java/com/google/devtools/build/lib
parent423a46a11acd109467573c3344a4dd49f211aea6 (diff)
Rewrite all the sandbox strategy implementations
- Make use of existing abstractions like SpawnRunner and SpawnExecutionPolicy. - Instead of having the *Strategy create a *Runner, and then call back into SandboxStrategy, create a single SandboxContainer which contains the full command line, environment, and everything needed to create and delete the sandbox directory. - Do all the work in SandboxStrategy, including creation and deletion of the sandbox directory. - Use SpawnResult instead of throwing, catching, and rethrowing. - Simplify the control flow a bit. PiperOrigin-RevId: 161644979
Diffstat (limited to 'src/main/java/com/google/devtools/build/lib')
-rw-r--r--src/main/java/com/google/devtools/build/lib/actions/ExecException.java18
-rw-r--r--src/main/java/com/google/devtools/build/lib/actions/UserExecException.java8
-rw-r--r--src/main/java/com/google/devtools/build/lib/exec/SpawnExecException.java5
-rw-r--r--src/main/java/com/google/devtools/build/lib/exec/SpawnRunner.java8
-rw-r--r--src/main/java/com/google/devtools/build/lib/remote/RemoteSpawnStrategy.java6
-rw-r--r--src/main/java/com/google/devtools/build/lib/sandbox/DarwinSandboxRunner.java145
-rw-r--r--src/main/java/com/google/devtools/build/lib/sandbox/DarwinSandboxedStrategy.java181
-rw-r--r--src/main/java/com/google/devtools/build/lib/sandbox/LinuxSandboxRunner.java164
-rw-r--r--src/main/java/com/google/devtools/build/lib/sandbox/LinuxSandboxedStrategy.java188
-rw-r--r--src/main/java/com/google/devtools/build/lib/sandbox/ProcessWrapperRunner.java28
-rw-r--r--src/main/java/com/google/devtools/build/lib/sandbox/ProcessWrapperSandboxedStrategy.java86
-rw-r--r--src/main/java/com/google/devtools/build/lib/sandbox/SandboxHelpers.java53
-rw-r--r--src/main/java/com/google/devtools/build/lib/sandbox/SandboxOptions.java20
-rw-r--r--src/main/java/com/google/devtools/build/lib/sandbox/SandboxRunner.java159
-rw-r--r--src/main/java/com/google/devtools/build/lib/sandbox/SandboxStrategy.java267
-rw-r--r--src/main/java/com/google/devtools/build/lib/sandbox/SandboxedSpawn.java (renamed from src/main/java/com/google/devtools/build/lib/sandbox/SandboxExecRoot.java)35
-rw-r--r--src/main/java/com/google/devtools/build/lib/sandbox/SymlinkedSandboxedSpawn.java (renamed from src/main/java/com/google/devtools/build/lib/sandbox/SymlinkedExecRoot.java)61
-rw-r--r--src/main/java/com/google/devtools/build/lib/standalone/StandaloneSpawnStrategy.java6
-rw-r--r--src/main/java/com/google/devtools/build/lib/worker/SandboxedWorker.java36
19 files changed, 659 insertions, 815 deletions
diff --git a/src/main/java/com/google/devtools/build/lib/actions/ExecException.java b/src/main/java/com/google/devtools/build/lib/actions/ExecException.java
index 9c81cc22a1..16f4550195 100644
--- a/src/main/java/com/google/devtools/build/lib/actions/ExecException.java
+++ b/src/main/java/com/google/devtools/build/lib/actions/ExecException.java
@@ -43,12 +43,10 @@ package com.google.devtools.build.lib.actions;
public abstract class ExecException extends Exception {
private final boolean catastrophe;
- private final boolean timedOut;
public ExecException(String message, boolean catastrophe) {
super(message);
this.catastrophe = catastrophe;
- this.timedOut = false;
}
public ExecException(String message) {
@@ -58,31 +56,17 @@ public abstract class ExecException extends Exception {
public ExecException(Throwable cause) {
super(cause);
this.catastrophe = false;
- this.timedOut = false;
}
public ExecException(String message, Throwable cause, boolean catastrophe) {
super(message + ": " + cause.getMessage(), cause);
this.catastrophe = catastrophe;
- this.timedOut = false;
}
public ExecException(String message, Throwable cause) {
this(message, cause, false);
}
- public ExecException(String message, boolean catastrophe, boolean timedOut) {
- super(message);
- this.catastrophe = catastrophe;
- this.timedOut = timedOut;
- }
-
- public ExecException(String message, Throwable cause, boolean catastrophe, boolean timedOut) {
- super(message, cause);
- this.catastrophe = catastrophe;
- this.timedOut = timedOut;
- }
-
/**
* Catastrophic exceptions should stop the build, even if --keep_going.
*/
@@ -120,6 +104,6 @@ public abstract class ExecException extends Exception {
* Tells if the execution exception was thrown because of the execution timing out.
*/
public boolean hasTimedOut() {
- return timedOut;
+ return false;
}
}
diff --git a/src/main/java/com/google/devtools/build/lib/actions/UserExecException.java b/src/main/java/com/google/devtools/build/lib/actions/UserExecException.java
index bdb52ab64e..5b06d3ecc3 100644
--- a/src/main/java/com/google/devtools/build/lib/actions/UserExecException.java
+++ b/src/main/java/com/google/devtools/build/lib/actions/UserExecException.java
@@ -28,18 +28,10 @@ public class UserExecException extends ExecException {
super(cause);
}
- public UserExecException(String message, boolean timedOut) {
- super(message, false, timedOut);
- }
-
public UserExecException(String message, Throwable cause) {
super(message, cause);
}
- public UserExecException(String message, Throwable cause, boolean timedOut) {
- super(message, cause, false, timedOut);
- }
-
@Override
public ActionExecutionException toActionExecutionException(String messagePrefix,
boolean verboseFailures, Action action) {
diff --git a/src/main/java/com/google/devtools/build/lib/exec/SpawnExecException.java b/src/main/java/com/google/devtools/build/lib/exec/SpawnExecException.java
index 70a6ae4db3..4c9bd95372 100644
--- a/src/main/java/com/google/devtools/build/lib/exec/SpawnExecException.java
+++ b/src/main/java/com/google/devtools/build/lib/exec/SpawnExecException.java
@@ -50,6 +50,11 @@ public class SpawnExecException extends ExecException {
}
@Override
+ public boolean hasTimedOut() {
+ return getSpawnResult().status() == Status.TIMEOUT;
+ }
+
+ @Override
public ActionExecutionException toActionExecutionException(String messagePrefix,
boolean verboseFailures, Action action) {
TerminationStatus status = new TerminationStatus(
diff --git a/src/main/java/com/google/devtools/build/lib/exec/SpawnRunner.java b/src/main/java/com/google/devtools/build/lib/exec/SpawnRunner.java
index a95f144ecc..9445171656 100644
--- a/src/main/java/com/google/devtools/build/lib/exec/SpawnRunner.java
+++ b/src/main/java/com/google/devtools/build/lib/exec/SpawnRunner.java
@@ -15,6 +15,7 @@ package com.google.devtools.build.lib.exec;
import com.google.devtools.build.lib.actions.ActionInput;
import com.google.devtools.build.lib.actions.ActionInputFileCache;
+import com.google.devtools.build.lib.actions.Artifact.ArtifactExpander;
import com.google.devtools.build.lib.actions.ExecException;
import com.google.devtools.build.lib.actions.Spawn;
import com.google.devtools.build.lib.util.io.FileOutErr;
@@ -122,6 +123,13 @@ public interface SpawnRunner {
/** The input file cache for this specific spawn. */
ActionInputFileCache getActionInputFileCache();
+ /** An artifact expander. */
+ // TODO(ulfjack): This is only used for the sandbox runners to compute a set of empty
+ // directories. We shouldn't have this and the getInputMapping method; maybe there's a way to
+ // unify the two? Alternatively, maybe the input mapping should (optionally?) contain
+ // directories? Or maybe we need a separate method to return the set of directories?
+ ArtifactExpander getArtifactExpander();
+
/**
* All implementations must call this method before writing to the provided stdout / stderr or
* to any of the output file locations. This method is used to coordinate - implementations
diff --git a/src/main/java/com/google/devtools/build/lib/remote/RemoteSpawnStrategy.java b/src/main/java/com/google/devtools/build/lib/remote/RemoteSpawnStrategy.java
index 9a1c415589..ea5e43a7d2 100644
--- a/src/main/java/com/google/devtools/build/lib/remote/RemoteSpawnStrategy.java
+++ b/src/main/java/com/google/devtools/build/lib/remote/RemoteSpawnStrategy.java
@@ -18,6 +18,7 @@ import com.google.devtools.build.lib.actions.ActionExecutionContext;
import com.google.devtools.build.lib.actions.ActionInput;
import com.google.devtools.build.lib.actions.ActionInputFileCache;
import com.google.devtools.build.lib.actions.ActionStatusMessage;
+import com.google.devtools.build.lib.actions.Artifact.ArtifactExpander;
import com.google.devtools.build.lib.actions.EnvironmentalExecException;
import com.google.devtools.build.lib.actions.ExecException;
import com.google.devtools.build.lib.actions.ExecutionStrategy;
@@ -87,6 +88,11 @@ final class RemoteSpawnStrategy implements SpawnActionContext {
}
@Override
+ public ArtifactExpander getArtifactExpander() {
+ return actionExecutionContext.getArtifactExpander();
+ }
+
+ @Override
public void lockOutputFiles() throws InterruptedException {
// This is only needed for the dynamic spawn strategy, which we still need to actually
// implement.
diff --git a/src/main/java/com/google/devtools/build/lib/sandbox/DarwinSandboxRunner.java b/src/main/java/com/google/devtools/build/lib/sandbox/DarwinSandboxRunner.java
deleted file mode 100644
index a30d96de72..0000000000
--- a/src/main/java/com/google/devtools/build/lib/sandbox/DarwinSandboxRunner.java
+++ /dev/null
@@ -1,145 +0,0 @@
-// Copyright 2014 The Bazel Authors. All rights reserved.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package com.google.devtools.build.lib.sandbox;
-
-import static java.nio.charset.StandardCharsets.UTF_8;
-
-import com.google.common.collect.ImmutableMap;
-import com.google.common.io.ByteStreams;
-import com.google.devtools.build.lib.runtime.CommandEnvironment;
-import com.google.devtools.build.lib.shell.Command;
-import com.google.devtools.build.lib.shell.CommandException;
-import com.google.devtools.build.lib.vfs.Path;
-import java.io.BufferedWriter;
-import java.io.File;
-import java.io.IOException;
-import java.io.OutputStreamWriter;
-import java.io.PrintWriter;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-
-/**
- * Helper class for running the namespace sandbox. This runner prepares environment inside the
- * sandbox, handles sandbox output, performs cleanup and changes invocation if necessary.
- */
-final class DarwinSandboxRunner extends SandboxRunner {
- private static final String SANDBOX_EXEC = "/usr/bin/sandbox-exec";
-
- private final Path sandboxExecRoot;
- private final Path sandboxConfigPath;
- private final Set<Path> writableDirs;
- private final Set<Path> inaccessiblePaths;
-
- DarwinSandboxRunner(
- Path sandboxPath,
- Path sandboxExecRoot,
- Set<Path> writableDirs,
- Set<Path> inaccessiblePaths,
- boolean verboseFailures) {
- super(verboseFailures);
- this.sandboxExecRoot = sandboxExecRoot;
- this.sandboxConfigPath = sandboxPath.getRelative("sandbox.sb");
- this.writableDirs = writableDirs;
- this.inaccessiblePaths = inaccessiblePaths;
- }
-
- static boolean isSupported(CommandEnvironment cmdEnv) {
- if (!ProcessWrapperRunner.isSupported(cmdEnv)) {
- return false;
- }
-
- List<String> args = new ArrayList<>();
- args.add(SANDBOX_EXEC);
- args.add("-p");
- args.add("(version 1) (allow default)");
- args.add("/usr/bin/true");
-
- ImmutableMap<String, String> env = ImmutableMap.of();
- File cwd = new File("/usr/bin");
-
- Command cmd = new Command(args.toArray(new String[0]), env, cwd);
- try {
- cmd.execute(
- /* stdin */ new byte[] {},
- Command.NO_OBSERVER,
- ByteStreams.nullOutputStream(),
- ByteStreams.nullOutputStream(),
- /* killSubprocessOnInterrupt */ true);
- } catch (CommandException e) {
- return false;
- }
-
- return true;
- }
-
- @Override
- protected Command getCommand(
- CommandEnvironment cmdEnv,
- List<String> arguments,
- Map<String, String> env,
- int timeout,
- boolean allowNetwork,
- boolean useFakeHostname,
- boolean useFakeUsername)
- throws IOException {
- writeConfig(allowNetwork);
-
- List<String> commandLineArgs = new ArrayList<>();
- commandLineArgs.add(SANDBOX_EXEC);
- commandLineArgs.add("-f");
- commandLineArgs.add(sandboxConfigPath.getPathString());
- commandLineArgs.addAll(ProcessWrapperRunner.getCommandLine(cmdEnv, arguments, timeout));
- return new Command(commandLineArgs.toArray(new String[0]), env, sandboxExecRoot.getPathFile());
- }
-
- private void writeConfig(boolean allowNetwork) throws IOException {
- try (PrintWriter out =
- new PrintWriter(
- new BufferedWriter(
- new OutputStreamWriter(sandboxConfigPath.getOutputStream(), UTF_8)))) {
- // Note: In Apple's sandbox configuration language, the *last* matching rule wins.
- out.println("(version 1)");
- out.println("(debug deny)");
- out.println("(allow default)");
-
- if (!allowNetwork) {
- out.println("(deny network*)");
- out.println("(allow network* (local ip \"localhost:*\"))");
- out.println("(allow network* (remote ip \"localhost:*\"))");
- }
-
- // By default, everything is read-only.
- out.println("(deny file-write*)");
-
- out.println("(allow file-write*");
- for (Path path : writableDirs) {
- out.println(" (subpath \"" + path.getPathString() + "\")");
- }
- out.println(")");
-
- if (!inaccessiblePaths.isEmpty()) {
- out.println("(deny file-read*");
- // The sandbox configuration file is not part of a cache key and sandbox-exec doesn't care
- // about ordering of paths in expressions, so it's fine if the iteration order is random.
- for (Path inaccessiblePath : inaccessiblePaths) {
- out.println(" (subpath \"" + inaccessiblePath + "\")");
- }
- out.println(")");
- }
- }
- }
-}
diff --git a/src/main/java/com/google/devtools/build/lib/sandbox/DarwinSandboxedStrategy.java b/src/main/java/com/google/devtools/build/lib/sandbox/DarwinSandboxedStrategy.java
index 9d8e5acfe9..89b961edf9 100644
--- a/src/main/java/com/google/devtools/build/lib/sandbox/DarwinSandboxedStrategy.java
+++ b/src/main/java/com/google/devtools/build/lib/sandbox/DarwinSandboxedStrategy.java
@@ -16,15 +16,16 @@ package com.google.devtools.build.lib.sandbox;
import static java.nio.charset.StandardCharsets.UTF_8;
+import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
-import com.google.devtools.build.lib.actions.ActionExecutionContext;
-import com.google.devtools.build.lib.actions.ActionStatusMessage;
+import com.google.common.io.ByteStreams;
import com.google.devtools.build.lib.actions.ExecException;
import com.google.devtools.build.lib.actions.ExecutionStrategy;
import com.google.devtools.build.lib.actions.Spawn;
import com.google.devtools.build.lib.actions.SpawnActionContext;
import com.google.devtools.build.lib.buildtool.BuildRequest;
-import com.google.devtools.build.lib.exec.SpawnInputExpander;
+import com.google.devtools.build.lib.exec.SpawnResult;
+import com.google.devtools.build.lib.exec.SpawnRunner.SpawnExecutionPolicy;
import com.google.devtools.build.lib.exec.apple.XCodeLocalEnvProvider;
import com.google.devtools.build.lib.exec.local.LocalEnvProvider;
import com.google.devtools.build.lib.runtime.CommandEnvironment;
@@ -33,14 +34,19 @@ import com.google.devtools.build.lib.shell.CommandException;
import com.google.devtools.build.lib.shell.CommandResult;
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 java.io.BufferedWriter;
+import java.io.File;
import java.io.IOException;
+import java.io.OutputStreamWriter;
+import java.io.PrintWriter;
+import java.util.ArrayList;
import java.util.HashSet;
+import java.util.List;
import java.util.Map;
import java.util.Set;
-import java.util.concurrent.atomic.AtomicReference;
+import java.util.concurrent.TimeUnit;
/** Strategy that uses sandboxing to execute a process, for Darwin */
@ExecutionStrategy(
@@ -48,16 +54,44 @@ import java.util.concurrent.atomic.AtomicReference;
contextType = SpawnActionContext.class
)
public class DarwinSandboxedStrategy extends SandboxStrategy {
+ private static final String SANDBOX_EXEC = "/usr/bin/sandbox-exec";
public static boolean isSupported(CommandEnvironment cmdEnv) {
- return OS.getCurrent() == OS.DARWIN && DarwinSandboxRunner.isSupported(cmdEnv);
+ if (OS.getCurrent() != OS.DARWIN) {
+ return false;
+ }
+ if (!ProcessWrapperRunner.isSupported(cmdEnv)) {
+ return false;
+ }
+
+ List<String> args = new ArrayList<>();
+ args.add(SANDBOX_EXEC);
+ args.add("-p");
+ args.add("(version 1) (allow default)");
+ args.add("/usr/bin/true");
+
+ ImmutableMap<String, String> env = ImmutableMap.of();
+ File cwd = new File("/usr/bin");
+
+ Command cmd = new Command(args.toArray(new String[0]), env, cwd);
+ try {
+ cmd.execute(
+ /* stdin */ new byte[] {},
+ Command.NO_OBSERVER,
+ ByteStreams.nullOutputStream(),
+ ByteStreams.nullOutputStream(),
+ /* killSubprocessOnInterrupt */ true);
+ } catch (CommandException e) {
+ return false;
+ }
+
+ return true;
}
private final Path execRoot;
- private final boolean sandboxDebug;
- private final boolean verboseFailures;
+ private final boolean allowNetwork;
private final String productName;
- private final SpawnInputExpander spawnInputExpander;
+ private final Path processWrapper;
/**
* The set of directories that always should be writable, independent of the Spawn itself.
@@ -76,16 +110,14 @@ public class DarwinSandboxedStrategy extends SandboxStrategy {
ImmutableSet<Path> alwaysWritableDirs) {
super(
cmdEnv,
- buildRequest,
sandboxBase,
verboseFailures,
buildRequest.getOptions(SandboxOptions.class));
this.execRoot = cmdEnv.getExecRoot();
- this.sandboxDebug = buildRequest.getOptions(SandboxOptions.class).sandboxDebug;
- this.verboseFailures = verboseFailures;
+ this.allowNetwork = SandboxHelpers.shouldAllowNetwork(cmdEnv.getOptions());
this.productName = productName;
this.alwaysWritableDirs = alwaysWritableDirs;
- this.spawnInputExpander = new SpawnInputExpander(false);
+ this.processWrapper = ProcessWrapperRunner.getProcessWrapper(cmdEnv);
this.localEnvProvider = new XCodeLocalEnvProvider();
}
@@ -161,16 +193,8 @@ public class DarwinSandboxedStrategy extends SandboxStrategy {
}
@Override
- protected void actuallyExec(
- Spawn spawn,
- ActionExecutionContext actionExecutionContext,
- AtomicReference<Class<? extends SpawnActionContext>> writeOutputFiles)
- throws ExecException, InterruptedException, IOException {
- actionExecutionContext
- .getEventBus()
- .post(ActionStatusMessage.runningStrategy(spawn.getResourceOwner(), "darwin-sandbox"));
- SandboxHelpers.reportSubcommand(actionExecutionContext, spawn);
-
+ protected SpawnResult actuallyExec(Spawn spawn, SpawnExecutionPolicy policy)
+ throws ExecException, IOException, InterruptedException {
// Each invocation of "exec" gets its own sandbox.
Path sandboxPath = getSandboxRoot();
Path sandboxExecRoot = sandboxPath.getRelative("execroot").getRelative(execRoot.getBaseName());
@@ -178,50 +202,91 @@ public class DarwinSandboxedStrategy extends SandboxStrategy {
Map<String, String> spawnEnvironment =
localEnvProvider.rewriteLocalEnv(spawn.getEnvironment(), execRoot, productName);
- HashSet<Path> writableDirs = new HashSet<>(alwaysWritableDirs);
+ final HashSet<Path> writableDirs = new HashSet<>(alwaysWritableDirs);
ImmutableSet<Path> extraWritableDirs = getWritableDirs(sandboxExecRoot, spawnEnvironment);
writableDirs.addAll(extraWritableDirs);
- SymlinkedExecRoot symlinkedExecRoot = new SymlinkedExecRoot(sandboxExecRoot);
ImmutableSet<PathFragment> outputs = SandboxHelpers.getOutputFiles(spawn);
- symlinkedExecRoot.createFileSystem(
- SandboxHelpers.getInputFiles(
- spawnInputExpander, this.execRoot, spawn, actionExecutionContext),
+
+ final Path sandboxConfigPath = sandboxPath.getRelative("sandbox.sb");
+ int timeoutSeconds = (int) TimeUnit.MILLISECONDS.toSeconds(policy.getTimeoutMillis());
+ List<String> arguments = computeCommandLine(
+ spawn, timeoutSeconds, sandboxConfigPath);
+ Map<String, String> environment =
+ localEnvProvider.rewriteLocalEnv(spawn.getEnvironment(), execRoot, productName);
+
+ boolean allowNetworkForThisSpawn = allowNetwork || SandboxHelpers.shouldAllowNetwork(spawn);
+ SandboxedSpawn sandbox = new SymlinkedSandboxedSpawn(
+ sandboxPath,
+ sandboxExecRoot,
+ arguments,
+ environment,
+ SandboxHelpers.getInputFiles(spawn, policy, execRoot),
outputs,
- writableDirs);
+ writableDirs) {
+ @Override
+ public void createFileSystem() throws IOException {
+ super.createFileSystem();
+ writeConfig(
+ sandboxConfigPath, writableDirs, getInaccessiblePaths(), allowNetworkForThisSpawn);
+ }
+ };
+ return runSpawn(spawn, sandbox, policy, execRoot, timeoutSeconds);
+ }
- // This will add the resolved versions of the spawn-dependant writable paths (e.g. its execroot
- // or TEST_TMPDIR) to the set, now that they have been created by the SymlinkedExecRoot.
- for (Path extraWritableDir : extraWritableDirs) {
- addPathToSetIfExists(writableDirs, extraWritableDir);
- }
+ private List<String> computeCommandLine(Spawn spawn, int timeoutSeconds, Path sandboxConfigPath) {
+ List<String> commandLineArgs = new ArrayList<>();
+ commandLineArgs.add(SANDBOX_EXEC);
+ commandLineArgs.add("-f");
+ commandLineArgs.add(sandboxConfigPath.getPathString());
+ commandLineArgs.addAll(
+ ProcessWrapperRunner.getCommandLine(processWrapper, spawn.getArguments(), timeoutSeconds));
+ return commandLineArgs;
+ }
- DarwinSandboxRunner runner =
- new DarwinSandboxRunner(
- sandboxPath, sandboxExecRoot, writableDirs, getInaccessiblePaths(), verboseFailures);
- try {
- runSpawn(
- spawn,
- actionExecutionContext,
- spawnEnvironment,
- symlinkedExecRoot,
- outputs,
- runner,
- writeOutputFiles);
- } finally {
- if (!sandboxDebug) {
- try {
- FileSystemUtils.deleteTree(sandboxPath);
- } catch (IOException e) {
- // This usually means that the Spawn itself exited, but still has children running that
- // we couldn't wait for, which now block deletion of the sandbox directory. On Linux this
- // should never happen, as we use PID namespaces and where they are not available the
- // subreaper feature to make sure all children have been reliably killed before returning,
- // but on other OS this might not always work. The SandboxModule will try to delete them
- // again when the build is all done, at which point it hopefully works, so let's just go
- // on here.
+ private void writeConfig(
+ Path sandboxConfigPath,
+ Set<Path> writableDirs,
+ Set<Path> inaccessiblePaths,
+ boolean allowNetwork) throws IOException {
+ try (PrintWriter out =
+ new PrintWriter(
+ new BufferedWriter(
+ new OutputStreamWriter(sandboxConfigPath.getOutputStream(), UTF_8)))) {
+ // Note: In Apple's sandbox configuration language, the *last* matching rule wins.
+ out.println("(version 1)");
+ out.println("(debug deny)");
+ out.println("(allow default)");
+
+ if (!allowNetwork) {
+ out.println("(deny network*)");
+ out.println("(allow network* (local ip \"localhost:*\"))");
+ out.println("(allow network* (remote ip \"localhost:*\"))");
+ }
+
+ // By default, everything is read-only.
+ out.println("(deny file-write*)");
+
+ out.println("(allow file-write*");
+ for (Path path : writableDirs) {
+ out.println(" (subpath \"" + path.getPathString() + "\")");
+ }
+ out.println(")");
+
+ if (!inaccessiblePaths.isEmpty()) {
+ out.println("(deny file-read*");
+ // The sandbox configuration file is not part of a cache key and sandbox-exec doesn't care
+ // about ordering of paths in expressions, so it's fine if the iteration order is random.
+ for (Path inaccessiblePath : inaccessiblePaths) {
+ out.println(" (subpath \"" + inaccessiblePath + "\")");
}
+ out.println(")");
}
}
}
+
+ @Override
+ protected String getName() {
+ return "darwin-sandbox";
+ }
}
diff --git a/src/main/java/com/google/devtools/build/lib/sandbox/LinuxSandboxRunner.java b/src/main/java/com/google/devtools/build/lib/sandbox/LinuxSandboxRunner.java
deleted file mode 100644
index a960a81bc4..0000000000
--- a/src/main/java/com/google/devtools/build/lib/sandbox/LinuxSandboxRunner.java
+++ /dev/null
@@ -1,164 +0,0 @@
-// Copyright 2014 The Bazel Authors. All rights reserved.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package com.google.devtools.build.lib.sandbox;
-
-import com.google.common.collect.ImmutableMap;
-import com.google.common.io.ByteStreams;
-import com.google.devtools.build.lib.runtime.CommandEnvironment;
-import com.google.devtools.build.lib.shell.Command;
-import com.google.devtools.build.lib.shell.CommandException;
-import com.google.devtools.build.lib.util.OsUtils;
-import com.google.devtools.build.lib.vfs.Path;
-import com.google.devtools.build.lib.vfs.PathFragment;
-import java.io.File;
-import java.io.IOException;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-
-/**
- * Helper class for running the Linux sandbox. This runner prepares environment inside the sandbox,
- * handles sandbox output, performs cleanup and changes invocation if necessary.
- */
-final class LinuxSandboxRunner extends SandboxRunner {
- private static final String LINUX_SANDBOX = "linux-sandbox" + OsUtils.executableExtension();
-
- private final Path sandboxExecRoot;
- private final Set<Path> writableDirs;
- private final Set<Path> tmpfsPaths;
- // a <target, source> mapping of paths to bind mount
- private final Map<Path, Path> bindMounts;
- private final boolean sandboxDebug;
-
- LinuxSandboxRunner(
- Path sandboxExecRoot,
- Set<Path> writableDirs,
- Set<Path> tmpfsPaths,
- Map<Path, Path> bindMounts,
- boolean verboseFailures,
- boolean sandboxDebug) {
- super(verboseFailures);
- this.sandboxExecRoot = sandboxExecRoot;
- this.writableDirs = writableDirs;
- this.tmpfsPaths = tmpfsPaths;
- this.bindMounts = bindMounts;
- this.sandboxDebug = sandboxDebug;
- }
-
- static boolean isSupported(CommandEnvironment cmdEnv) {
- Path embeddedTool = getLinuxSandbox(cmdEnv);
- if (embeddedTool == null) {
- // The embedded tool does not exist, meaning that we don't support sandboxing (e.g., while
- // bootstrapping).
- return false;
- }
-
- Path execRoot = cmdEnv.getExecRoot();
-
- List<String> args = new ArrayList<>();
- args.add(embeddedTool.getPathString());
- args.add("--");
- args.add("/bin/true");
-
- ImmutableMap<String, String> env = ImmutableMap.of();
- File cwd = execRoot.getPathFile();
-
- Command cmd = new Command(args.toArray(new String[0]), env, cwd);
- try {
- cmd.execute(
- /* stdin */ new byte[] {},
- Command.NO_OBSERVER,
- ByteStreams.nullOutputStream(),
- ByteStreams.nullOutputStream(),
- /* killSubprocessOnInterrupt */ true);
- } catch (CommandException e) {
- return false;
- }
-
- return true;
- }
-
- private static Path getLinuxSandbox(CommandEnvironment cmdEnv) {
- PathFragment execPath = cmdEnv.getBlazeWorkspace().getBinTools().getExecPath(LINUX_SANDBOX);
- return execPath != null ? cmdEnv.getExecRoot().getRelative(execPath) : null;
- }
-
- @Override
- protected Command getCommand(
- CommandEnvironment cmdEnv,
- List<String> spawnArguments,
- Map<String, String> env,
- int timeout,
- boolean allowNetwork,
- boolean useFakeHostname,
- boolean useFakeUsername)
- throws IOException {
- List<String> commandLineArgs = new ArrayList<>();
- commandLineArgs.add(getLinuxSandbox(cmdEnv).getPathString());
-
- if (sandboxDebug) {
- commandLineArgs.add("-D");
- }
-
- // Kill the process after a timeout.
- if (timeout != -1) {
- commandLineArgs.add("-T");
- commandLineArgs.add(Integer.toString(timeout));
- }
-
- // Create all needed directories.
- for (Path writablePath : writableDirs) {
- commandLineArgs.add("-w");
- commandLineArgs.add(writablePath.getPathString());
- }
-
- for (Path tmpfsPath : tmpfsPaths) {
- commandLineArgs.add("-e");
- commandLineArgs.add(tmpfsPath.getPathString());
- }
-
- for (ImmutableMap.Entry<Path, Path> bindMount : bindMounts.entrySet()) {
- commandLineArgs.add("-M");
- commandLineArgs.add(bindMount.getValue().getPathString());
-
- // The file is mounted in a custom location inside the sandbox.
- if (!bindMount.getKey().equals(bindMount.getValue())) {
- commandLineArgs.add("-m");
- commandLineArgs.add(bindMount.getKey().getPathString());
- }
- }
-
- if (!allowNetwork) {
- // Block network access out of the namespace.
- commandLineArgs.add("-N");
- }
-
- if (useFakeHostname) {
- // Use a fake hostname ("localhost") inside the sandbox.
- commandLineArgs.add("-H");
- }
-
- if (useFakeUsername) {
- // Use a fake username ("nobody") inside the sandbox.
- commandLineArgs.add("-U");
- }
-
- commandLineArgs.add("--");
- commandLineArgs.addAll(spawnArguments);
- return new Command(commandLineArgs.toArray(new String[0]), env, sandboxExecRoot.getPathFile());
- }
-
-}
diff --git a/src/main/java/com/google/devtools/build/lib/sandbox/LinuxSandboxedStrategy.java b/src/main/java/com/google/devtools/build/lib/sandbox/LinuxSandboxedStrategy.java
index 1d6e5ca4fd..f0d3031e66 100644
--- a/src/main/java/com/google/devtools/build/lib/sandbox/LinuxSandboxedStrategy.java
+++ b/src/main/java/com/google/devtools/build/lib/sandbox/LinuxSandboxedStrategy.java
@@ -17,8 +17,7 @@ package com.google.devtools.build.lib.sandbox;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Maps;
-import com.google.devtools.build.lib.actions.ActionExecutionContext;
-import com.google.devtools.build.lib.actions.ActionStatusMessage;
+import com.google.common.io.ByteStreams;
import com.google.devtools.build.lib.actions.ExecException;
import com.google.devtools.build.lib.actions.ExecutionStrategy;
import com.google.devtools.build.lib.actions.Spawn;
@@ -26,19 +25,25 @@ import com.google.devtools.build.lib.actions.SpawnActionContext;
import com.google.devtools.build.lib.actions.UserExecException;
import com.google.devtools.build.lib.analysis.BlazeDirectories;
import com.google.devtools.build.lib.buildtool.BuildRequest;
-import com.google.devtools.build.lib.exec.SpawnInputExpander;
+import com.google.devtools.build.lib.exec.SpawnResult;
+import com.google.devtools.build.lib.exec.SpawnRunner.SpawnExecutionPolicy;
import com.google.devtools.build.lib.runtime.CommandEnvironment;
+import com.google.devtools.build.lib.shell.Command;
+import com.google.devtools.build.lib.shell.CommandException;
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.build.lib.vfs.Symlinks;
+import java.io.File;
import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.SortedMap;
-import java.util.concurrent.atomic.AtomicReference;
+import java.util.concurrent.TimeUnit;
/** Strategy that uses sandboxing to execute a process. */
@ExecutionStrategy(
@@ -46,16 +51,54 @@ import java.util.concurrent.atomic.AtomicReference;
contextType = SpawnActionContext.class
)
public class LinuxSandboxedStrategy extends SandboxStrategy {
+ private static final String LINUX_SANDBOX = "linux-sandbox";
public static boolean isSupported(CommandEnvironment cmdEnv) {
- return OS.getCurrent() == OS.LINUX && LinuxSandboxRunner.isSupported(cmdEnv);
+ if (OS.getCurrent() != OS.LINUX) {
+ return false;
+ }
+ Path embeddedTool = getLinuxSandbox(cmdEnv);
+ if (embeddedTool == null) {
+ // The embedded tool does not exist, meaning that we don't support sandboxing (e.g., while
+ // bootstrapping).
+ return false;
+ }
+
+ Path execRoot = cmdEnv.getExecRoot();
+
+ List<String> args = new ArrayList<>();
+ args.add(embeddedTool.getPathString());
+ args.add("--");
+ args.add("/bin/true");
+
+ ImmutableMap<String, String> env = ImmutableMap.of();
+ File cwd = execRoot.getPathFile();
+
+ Command cmd = new Command(args.toArray(new String[0]), env, cwd);
+ try {
+ cmd.execute(
+ /* stdin */ new byte[] {},
+ Command.NO_OBSERVER,
+ ByteStreams.nullOutputStream(),
+ ByteStreams.nullOutputStream(),
+ /* killSubprocessOnInterrupt */ true);
+ } catch (CommandException e) {
+ return false;
+ }
+
+ return true;
+ }
+
+ private static Path getLinuxSandbox(CommandEnvironment cmdEnv) {
+ PathFragment execPath = cmdEnv.getBlazeWorkspace().getBinTools().getExecPath(LINUX_SANDBOX);
+ return execPath != null ? cmdEnv.getExecRoot().getRelative(execPath) : null;
}
private final SandboxOptions sandboxOptions;
private final BlazeDirectories blazeDirs;
private final Path execRoot;
- private final boolean verboseFailures;
- private final SpawnInputExpander spawnInputExpander;
+ private final boolean allowNetwork;
+ private final Path linuxSandbox;
private final Path inaccessibleHelperFile;
private final Path inaccessibleHelperDir;
@@ -68,15 +111,14 @@ public class LinuxSandboxedStrategy extends SandboxStrategy {
Path inaccessibleHelperDir) {
super(
cmdEnv,
- buildRequest,
sandboxBase,
verboseFailures,
buildRequest.getOptions(SandboxOptions.class));
- this.sandboxOptions = buildRequest.getOptions(SandboxOptions.class);
+ this.sandboxOptions = cmdEnv.getOptions().getOptions(SandboxOptions.class);
this.blazeDirs = cmdEnv.getDirectories();
this.execRoot = cmdEnv.getExecRoot();
- this.verboseFailures = verboseFailures;
- this.spawnInputExpander = new SpawnInputExpander(false);
+ this.allowNetwork = SandboxHelpers.shouldAllowNetwork(cmdEnv.getOptions());
+ this.linuxSandbox = getLinuxSandbox(cmdEnv);
this.inaccessibleHelperFile = inaccessibleHelperFile;
this.inaccessibleHelperDir = inaccessibleHelperDir;
}
@@ -109,61 +151,101 @@ public class LinuxSandboxedStrategy extends SandboxStrategy {
}
@Override
- protected void actuallyExec(
- Spawn spawn,
- ActionExecutionContext actionExecutionContext,
- AtomicReference<Class<? extends SpawnActionContext>> writeOutputFiles)
+ protected SpawnResult actuallyExec(Spawn spawn, SpawnExecutionPolicy policy)
throws IOException, ExecException, InterruptedException {
- actionExecutionContext
- .getEventBus()
- .post(ActionStatusMessage.runningStrategy(spawn.getResourceOwner(), "linux-sandbox"));
- SandboxHelpers.reportSubcommand(actionExecutionContext, spawn);
-
// Each invocation of "exec" gets its own sandbox.
Path sandboxPath = getSandboxRoot();
Path sandboxExecRoot = sandboxPath.getRelative("execroot").getRelative(execRoot.getBaseName());
Set<Path> writableDirs = getWritableDirs(sandboxExecRoot, spawn.getEnvironment());
- SymlinkedExecRoot symlinkedExecRoot = new SymlinkedExecRoot(sandboxExecRoot);
ImmutableSet<PathFragment> outputs = SandboxHelpers.getOutputFiles(spawn);
- symlinkedExecRoot.createFileSystem(
- SandboxHelpers.getInputFiles(spawnInputExpander, execRoot, spawn, actionExecutionContext),
+ int timeoutSeconds = (int) TimeUnit.MILLISECONDS.toSeconds(policy.getTimeoutMillis());
+ List<String> arguments = computeCommandLine(
+ spawn,
+ timeoutSeconds,
+ linuxSandbox,
+ writableDirs,
+ getTmpfsPaths(),
+ getReadOnlyBindMounts(blazeDirs, sandboxExecRoot),
+ allowNetwork || SandboxHelpers.shouldAllowNetwork(spawn));
+
+ SandboxedSpawn sandbox = new SymlinkedSandboxedSpawn(
+ sandboxPath,
+ sandboxExecRoot,
+ arguments,
+ spawn.getEnvironment(),
+ SandboxHelpers.getInputFiles(spawn, policy, execRoot),
outputs,
writableDirs);
+ return runSpawn(spawn, sandbox, policy, execRoot, timeoutSeconds);
+ }
- SandboxRunner runner =
- new LinuxSandboxRunner(
- sandboxExecRoot,
- writableDirs,
- getTmpfsPaths(),
- getReadOnlyBindMounts(blazeDirs, sandboxExecRoot),
- verboseFailures,
- sandboxOptions.sandboxDebug);
+ private List<String> computeCommandLine(
+ Spawn spawn,
+ int timeoutSeconds,
+ Path linuxSandbox,
+ Set<Path> writableDirs,
+ Set<Path> tmpfsPaths,
+ Map<Path, Path> bindMounts,
+ boolean allowNetwork) {
+ List<String> commandLineArgs = new ArrayList<>();
+ commandLineArgs.add(linuxSandbox.getPathString());
- try {
- runSpawn(
- spawn,
- actionExecutionContext,
- spawn.getEnvironment(),
- symlinkedExecRoot,
- outputs,
- runner,
- writeOutputFiles);
- } finally {
- if (!sandboxOptions.sandboxDebug) {
- try {
- FileSystemUtils.deleteTree(sandboxPath);
- } catch (IOException e) {
- // This usually means that the Spawn itself exited, but still has children running that
- // we couldn't wait for, which now block deletion of the sandbox directory. On Linux this
- // should never happen, as we use PID namespaces and where they are not available the
- // subreaper feature to make sure all children have been reliably killed before returning,
- // but on other OS this might not always work. The SandboxModule will try to delete them
- // again when the build is all done, at which point it hopefully works, so let's just go
- // on here.
- }
+ if (sandboxOptions.sandboxDebug) {
+ commandLineArgs.add("-D");
+ }
+
+ // Kill the process after a timeout.
+ if (timeoutSeconds != -1) {
+ commandLineArgs.add("-T");
+ commandLineArgs.add(Integer.toString(timeoutSeconds));
+ }
+
+ // Create all needed directories.
+ for (Path writablePath : writableDirs) {
+ commandLineArgs.add("-w");
+ commandLineArgs.add(writablePath.getPathString());
+ }
+
+ for (Path tmpfsPath : tmpfsPaths) {
+ commandLineArgs.add("-e");
+ commandLineArgs.add(tmpfsPath.getPathString());
+ }
+
+ for (ImmutableMap.Entry<Path, Path> bindMount : bindMounts.entrySet()) {
+ commandLineArgs.add("-M");
+ commandLineArgs.add(bindMount.getValue().getPathString());
+
+ // The file is mounted in a custom location inside the sandbox.
+ if (!bindMount.getKey().equals(bindMount.getValue())) {
+ commandLineArgs.add("-m");
+ commandLineArgs.add(bindMount.getKey().getPathString());
}
}
+
+ if (!allowNetwork) {
+ // Block network access out of the namespace.
+ commandLineArgs.add("-N");
+ }
+
+ if (sandboxOptions.sandboxFakeHostname) {
+ // Use a fake hostname ("localhost") inside the sandbox.
+ commandLineArgs.add("-H");
+ }
+
+ if (sandboxOptions.sandboxFakeUsername) {
+ // Use a fake username ("nobody") inside the sandbox.
+ commandLineArgs.add("-U");
+ }
+
+ commandLineArgs.add("--");
+ commandLineArgs.addAll(spawn.getArguments());
+ return commandLineArgs;
+ }
+
+ @Override
+ protected String getName() {
+ return "linux-sandbox";
}
@Override
diff --git a/src/main/java/com/google/devtools/build/lib/sandbox/ProcessWrapperRunner.java b/src/main/java/com/google/devtools/build/lib/sandbox/ProcessWrapperRunner.java
index e6a3061da5..1dace949e9 100644
--- a/src/main/java/com/google/devtools/build/lib/sandbox/ProcessWrapperRunner.java
+++ b/src/main/java/com/google/devtools/build/lib/sandbox/ProcessWrapperRunner.java
@@ -15,28 +15,19 @@
package com.google.devtools.build.lib.sandbox;
import com.google.devtools.build.lib.runtime.CommandEnvironment;
-import com.google.devtools.build.lib.shell.Command;
import com.google.devtools.build.lib.util.OsUtils;
import com.google.devtools.build.lib.vfs.Path;
import com.google.devtools.build.lib.vfs.PathFragment;
import java.util.ArrayList;
import java.util.List;
-import java.util.Map;
/**
* This runner runs process-wrapper inside a sandboxed execution root, which should work on most
* platforms and gives at least some isolation between running actions.
*/
-final class ProcessWrapperRunner extends SandboxRunner {
+final class ProcessWrapperRunner {
private static final String PROCESS_WRAPPER = "process-wrapper" + OsUtils.executableExtension();
- private final Path sandboxExecRoot;
-
- ProcessWrapperRunner(Path sandboxExecRoot, boolean verboseFailures) {
- super(verboseFailures);
- this.sandboxExecRoot = sandboxExecRoot;
- }
-
static boolean isSupported(CommandEnvironment cmdEnv) {
// We can only use this runner, if the process-wrapper exists in the embedded tools.
// This might not always be the case, e.g. while bootstrapping.
@@ -49,23 +40,10 @@ final class ProcessWrapperRunner extends SandboxRunner {
return execPath != null ? cmdEnv.getExecRoot().getRelative(execPath) : null;
}
- @Override
- protected Command getCommand(
- CommandEnvironment cmdEnv,
- List<String> spawnArguments,
- Map<String, String> env,
- int timeout,
- boolean allowNetwork,
- boolean useFakeHostname,
- boolean useFakeUsername) {
- List<String> commandLineArgs = getCommandLine(cmdEnv, spawnArguments, timeout);
- return new Command(commandLineArgs.toArray(new String[0]), env, sandboxExecRoot.getPathFile());
- }
-
static List<String> getCommandLine(
- CommandEnvironment cmdEnv, List<String> spawnArguments, int timeout) {
+ Path processWrapper, List<String> spawnArguments, int timeout) {
List<String> commandLineArgs = new ArrayList<>(5 + spawnArguments.size());
- commandLineArgs.add(getProcessWrapper(cmdEnv).getPathString());
+ commandLineArgs.add(processWrapper.getPathString());
commandLineArgs.add("--timeout=" + timeout);
commandLineArgs.add("--kill_delay=5"); /* give some time to print stacktraces and whatnot. */
commandLineArgs.addAll(spawnArguments);
diff --git a/src/main/java/com/google/devtools/build/lib/sandbox/ProcessWrapperSandboxedStrategy.java b/src/main/java/com/google/devtools/build/lib/sandbox/ProcessWrapperSandboxedStrategy.java
index 0ccdd989e3..6fed1505ae 100644
--- a/src/main/java/com/google/devtools/build/lib/sandbox/ProcessWrapperSandboxedStrategy.java
+++ b/src/main/java/com/google/devtools/build/lib/sandbox/ProcessWrapperSandboxedStrategy.java
@@ -14,26 +14,22 @@
package com.google.devtools.build.lib.sandbox;
-import com.google.common.collect.ImmutableSet;
-import com.google.devtools.build.lib.actions.ActionExecutionContext;
-import com.google.devtools.build.lib.actions.ActionStatusMessage;
import com.google.devtools.build.lib.actions.ExecException;
import com.google.devtools.build.lib.actions.ExecutionStrategy;
import com.google.devtools.build.lib.actions.Spawn;
import com.google.devtools.build.lib.actions.SpawnActionContext;
import com.google.devtools.build.lib.buildtool.BuildRequest;
-import com.google.devtools.build.lib.exec.SpawnInputExpander;
+import com.google.devtools.build.lib.exec.SpawnResult;
+import com.google.devtools.build.lib.exec.SpawnRunner.SpawnExecutionPolicy;
import com.google.devtools.build.lib.exec.apple.XCodeLocalEnvProvider;
import com.google.devtools.build.lib.exec.local.LocalEnvProvider;
import com.google.devtools.build.lib.runtime.CommandEnvironment;
import com.google.devtools.build.lib.util.OS;
-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 java.io.IOException;
+import java.util.List;
import java.util.Map;
-import java.util.Set;
-import java.util.concurrent.atomic.AtomicReference;
+import java.util.concurrent.TimeUnit;
/** Strategy that uses sandboxing to execute a process. */
@ExecutionStrategy(
@@ -46,11 +42,9 @@ public class ProcessWrapperSandboxedStrategy extends SandboxStrategy {
return OS.isPosixCompatible() && ProcessWrapperRunner.isSupported(cmdEnv);
}
- private final SandboxOptions sandboxOptions;
private final Path execRoot;
- private final boolean verboseFailures;
private final String productName;
- private final SpawnInputExpander spawnInputExpander;
+ private final Path processWrapper;
private final LocalEnvProvider localEnvProvider;
ProcessWrapperSandboxedStrategy(
@@ -61,73 +55,43 @@ public class ProcessWrapperSandboxedStrategy extends SandboxStrategy {
String productName) {
super(
cmdEnv,
- buildRequest,
sandboxBase,
verboseFailures,
buildRequest.getOptions(SandboxOptions.class));
- this.sandboxOptions = buildRequest.getOptions(SandboxOptions.class);
this.execRoot = cmdEnv.getExecRoot();
- this.verboseFailures = verboseFailures;
this.productName = productName;
- this.spawnInputExpander = new SpawnInputExpander(false);
+ this.processWrapper = ProcessWrapperRunner.getProcessWrapper(cmdEnv);
this.localEnvProvider = OS.getCurrent() == OS.DARWIN
? new XCodeLocalEnvProvider()
: LocalEnvProvider.UNMODIFIED;
}
@Override
- protected void actuallyExec(
- Spawn spawn,
- ActionExecutionContext actionExecutionContext,
- AtomicReference<Class<? extends SpawnActionContext>> writeOutputFiles)
- throws ExecException, InterruptedException, IOException {
- actionExecutionContext
- .getEventBus()
- .post(
- ActionStatusMessage.runningStrategy(
- spawn.getResourceOwner(), "processwrapper-sandbox"));
- SandboxHelpers.reportSubcommand(actionExecutionContext, spawn);
-
+ protected SpawnResult actuallyExec(Spawn spawn, SpawnExecutionPolicy policy)
+ throws ExecException, IOException, InterruptedException {
// Each invocation of "exec" gets its own sandbox.
Path sandboxPath = getSandboxRoot();
Path sandboxExecRoot = sandboxPath.getRelative("execroot").getRelative(execRoot.getBaseName());
- Map<String, String> spawnEnvironment =
+ int timeoutSeconds = (int) TimeUnit.MILLISECONDS.toSeconds(policy.getTimeoutMillis());
+ List<String> arguments =
+ ProcessWrapperRunner.getCommandLine(processWrapper, spawn.getArguments(), timeoutSeconds);
+ Map<String, String> environment =
localEnvProvider.rewriteLocalEnv(spawn.getEnvironment(), execRoot, productName);
- Set<Path> writableDirs = getWritableDirs(sandboxExecRoot, spawn.getEnvironment());
- SymlinkedExecRoot symlinkedExecRoot = new SymlinkedExecRoot(sandboxExecRoot);
- ImmutableSet<PathFragment> outputs = SandboxHelpers.getOutputFiles(spawn);
- symlinkedExecRoot.createFileSystem(
- SandboxHelpers.getInputFiles(
- spawnInputExpander, this.execRoot, spawn, actionExecutionContext),
- outputs,
- writableDirs);
+ SandboxedSpawn sandbox = new SymlinkedSandboxedSpawn(
+ sandboxPath,
+ sandboxExecRoot,
+ arguments,
+ environment,
+ SandboxHelpers.getInputFiles(spawn, policy, execRoot),
+ SandboxHelpers.getOutputFiles(spawn),
+ getWritableDirs(sandboxExecRoot, spawn.getEnvironment()));
+ return runSpawn(spawn, sandbox, policy, execRoot, timeoutSeconds);
+ }
- SandboxRunner runner = new ProcessWrapperRunner(sandboxExecRoot, verboseFailures);
- try {
- runSpawn(
- spawn,
- actionExecutionContext,
- spawnEnvironment,
- symlinkedExecRoot,
- outputs,
- runner,
- writeOutputFiles);
- } finally {
- if (!sandboxOptions.sandboxDebug) {
- try {
- FileSystemUtils.deleteTree(sandboxPath);
- } catch (IOException e) {
- // This usually means that the Spawn itself exited, but still has children running that
- // we couldn't wait for, which now block deletion of the sandbox directory. On Linux this
- // should never happen, as we use PID namespaces and where they are not available the
- // subreaper feature to make sure all children have been reliably killed before returning,
- // but on other OS this might not always work. The SandboxModule will try to delete them
- // again when the build is all done, at which point it hopefully works, so let's just go
- // on here.
- }
- }
- }
+ @Override
+ protected String getName() {
+ return "processwrapper-sandbox";
}
}
diff --git a/src/main/java/com/google/devtools/build/lib/sandbox/SandboxHelpers.java b/src/main/java/com/google/devtools/build/lib/sandbox/SandboxHelpers.java
index e7d605fb37..e26f37bb43 100644
--- a/src/main/java/com/google/devtools/build/lib/sandbox/SandboxHelpers.java
+++ b/src/main/java/com/google/devtools/build/lib/sandbox/SandboxHelpers.java
@@ -19,16 +19,18 @@ import com.google.common.collect.ImmutableSet.Builder;
import com.google.devtools.build.lib.actions.ActionExecutionContext;
import com.google.devtools.build.lib.actions.ActionInput;
import com.google.devtools.build.lib.actions.Artifact;
+import com.google.devtools.build.lib.actions.Artifact.ArtifactExpander;
import com.google.devtools.build.lib.actions.ExecException;
import com.google.devtools.build.lib.actions.Spawn;
import com.google.devtools.build.lib.analysis.config.BuildConfiguration;
-import com.google.devtools.build.lib.buildtool.BuildRequest;
import com.google.devtools.build.lib.exec.SpawnInputExpander;
+import com.google.devtools.build.lib.exec.SpawnRunner.SpawnExecutionPolicy;
import com.google.devtools.build.lib.rules.fileset.FilesetActionContext;
import com.google.devtools.build.lib.standalone.StandaloneSpawnStrategy;
import com.google.devtools.build.lib.util.Preconditions;
import com.google.devtools.build.lib.vfs.Path;
import com.google.devtools.build.lib.vfs.PathFragment;
+import com.google.devtools.common.options.OptionsProvider;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
@@ -47,12 +49,6 @@ public final class SandboxHelpers {
standaloneStrategy.exec(spawn, actionExecutionContext);
}
- static void reportSubcommand(ActionExecutionContext actionExecutionContext, Spawn spawn) {
- if (actionExecutionContext.reportsSubcommands()) {
- actionExecutionContext.reportSubcommand(spawn);
- }
- }
-
/**
* Returns the inputs of a Spawn as a map of PathFragments relative to an execRoot to paths in the
* host filesystem where the input files can be found.
@@ -69,6 +65,30 @@ public final class SandboxHelpers {
executionContext.getArtifactExpander(),
executionContext.getActionInputFileCache(),
executionContext.getContext(FilesetActionContext.class));
+ return postProcess(inputMap, spawn, executionContext.getArtifactExpander(), execRoot);
+ }
+
+ /**
+ * Returns the inputs of a Spawn as a map of PathFragments relative to an execRoot to paths in the
+ * host filesystem where the input files can be found.
+ */
+ public static Map<PathFragment, Path> getInputFiles(
+ Spawn spawn,
+ SpawnExecutionPolicy policy,
+ Path execRoot)
+ throws IOException {
+ return postProcess(policy.getInputMapping(), spawn, policy.getArtifactExpander(), execRoot);
+ }
+
+ /**
+ * Returns the inputs of a Spawn as a map of PathFragments relative to an execRoot to paths in the
+ * host filesystem where the input files can be found.
+ */
+ private static Map<PathFragment, Path> postProcess(
+ Map<PathFragment, ActionInput> inputMap,
+ Spawn spawn,
+ ArtifactExpander artifactExpander,
+ Path execRoot) {
// SpawnInputExpander#getInputMapping uses ArtifactExpander#expandArtifacts to expand
// middlemen and tree artifacts, which expands empty tree artifacts to no entry. However,
// actions that accept TreeArtifacts as inputs generally expect that the empty directory is
@@ -77,7 +97,7 @@ public final class SandboxHelpers {
for (ActionInput input : spawn.getInputFiles()) {
if (input instanceof Artifact && ((Artifact) input).isTreeArtifact()) {
List<Artifact> containedArtifacts = new ArrayList<>();
- executionContext.getArtifactExpander().expand((Artifact) input, containedArtifacts);
+ artifactExpander.expand((Artifact) input, containedArtifacts);
// Attempting to mount a non-empty directory results in ERR_DIRECTORY_NOT_EMPTY, so we
// only mount empty TreeArtifacts as directories.
if (containedArtifacts.isEmpty()) {
@@ -105,17 +125,24 @@ public final class SandboxHelpers {
return outputFiles.build();
}
- static boolean shouldAllowNetwork(BuildRequest buildRequest, Spawn spawn) {
+ /**
+ * Returns true if the build options are set in a way that requires network access for all
+ * actions. This is separate from {@link #shouldAllowNetwork(Spawn)} to avoid having to keep a
+ * reference to the full set of build options (and also for performance, since this only needs to
+ * be checked once-per-build).
+ */
+ static boolean shouldAllowNetwork(OptionsProvider buildOptions) {
// Allow network access, when --java_debug is specified, otherwise we can't connect to the
// remote debug server of the test. This intentionally overrides the "block-network" execution
// tag.
- if (buildRequest
+ return buildOptions
.getOptions(BuildConfiguration.Options.class)
.testArguments
- .contains("--wrapper_script_flag=--debug")) {
- return true;
- }
+ .contains("--wrapper_script_flag=--debug");
+ }
+ /** Returns true if this specific spawn requires network access. */
+ static boolean shouldAllowNetwork(Spawn spawn) {
// If the Spawn requests to block network access, do so.
if (spawn.getExecutionInfo().containsKey("block-network")) {
return false;
diff --git a/src/main/java/com/google/devtools/build/lib/sandbox/SandboxOptions.java b/src/main/java/com/google/devtools/build/lib/sandbox/SandboxOptions.java
index 36a2aa9c01..d9949fdf4a 100644
--- a/src/main/java/com/google/devtools/build/lib/sandbox/SandboxOptions.java
+++ b/src/main/java/com/google/devtools/build/lib/sandbox/SandboxOptions.java
@@ -15,14 +15,19 @@
package com.google.devtools.build.lib.sandbox;
import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
+import com.google.devtools.build.lib.vfs.FileSystem;
+import com.google.devtools.build.lib.vfs.Path;
import com.google.devtools.common.options.Converter;
import com.google.devtools.common.options.Option;
import com.google.devtools.common.options.OptionDocumentationCategory;
import com.google.devtools.common.options.OptionsBase;
import com.google.devtools.common.options.OptionsParsingException;
import com.google.devtools.common.options.proto.OptionFilters.OptionEffectTag;
+import java.io.IOException;
+import java.util.ArrayList;
import java.util.List;
/** Options for sandboxed execution. */
@@ -173,4 +178,19 @@ public class SandboxOptions extends OptionsBase {
help = "Add additional path pair to mount in sandbox."
)
public List<ImmutableMap.Entry<String, String>> sandboxAdditionalMounts;
+
+ public ImmutableSet<Path> getInaccessiblePaths(FileSystem fs) {
+ List<Path> inaccessiblePaths = new ArrayList<>();
+ for (String path : sandboxBlockPath) {
+ Path blockedPath = fs.getPath(path);
+ try {
+ inaccessiblePaths.add(blockedPath.resolveSymbolicLinks());
+ } catch (IOException e) {
+ // It's OK to block access to an invalid symlink. In this case we'll just make the symlink
+ // itself inaccessible, instead of the target, though.
+ inaccessiblePaths.add(blockedPath);
+ }
+ }
+ return ImmutableSet.copyOf(inaccessiblePaths);
+ }
}
diff --git a/src/main/java/com/google/devtools/build/lib/sandbox/SandboxRunner.java b/src/main/java/com/google/devtools/build/lib/sandbox/SandboxRunner.java
deleted file mode 100644
index 9591402024..0000000000
--- a/src/main/java/com/google/devtools/build/lib/sandbox/SandboxRunner.java
+++ /dev/null
@@ -1,159 +0,0 @@
-// Copyright 2016 The Bazel Authors. All rights reserved.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package com.google.devtools.build.lib.sandbox;
-
-import com.google.devtools.build.lib.actions.ExecException;
-import com.google.devtools.build.lib.actions.UserExecException;
-import com.google.devtools.build.lib.runtime.CommandEnvironment;
-import com.google.devtools.build.lib.shell.AbnormalTerminationException;
-import com.google.devtools.build.lib.shell.Command;
-import com.google.devtools.build.lib.shell.CommandException;
-import com.google.devtools.build.lib.shell.KillableObserver;
-import com.google.devtools.build.lib.shell.TerminationStatus;
-import com.google.devtools.build.lib.util.CommandFailureUtils;
-import com.google.devtools.build.lib.util.io.OutErr;
-import java.io.IOException;
-import java.util.Arrays;
-import java.util.List;
-import java.util.Map;
-
-/** A common interface of all sandbox runners, no matter which platform they're working on. */
-abstract class SandboxRunner {
-
- private static final String SANDBOX_DEBUG_SUGGESTION =
- "\n\nUse --sandbox_debug to see verbose messages from the sandbox";
-
- private final boolean verboseFailures;
-
- SandboxRunner(boolean verboseFailures) {
- this.verboseFailures = verboseFailures;
- }
-
- /**
- * Runs the command specified via {@code arguments} and {@code env} inside the sandbox.
- *
- * @param cmdEnv - the current command environment.
- * @param arguments - arguments of spawn to run inside the sandbox.
- * @param environment - environment variables to pass to the spawn.
- * @param outErr - error output to capture sandbox's and command's stderr.
- * @param timeout - after how many seconds should the process be killed.
- * @param allowNetwork - whether networking should be allowed for the process.
- * @param sandboxDebug - whether debugging message should be printed.
- * @param useFakeHostname - whether the hostname should be set to 'localhost' inside the sandbox.
- * @param useFakeUsername - whether the username should be set to 'nobody' inside the sandbox.
- */
- void run(
- CommandEnvironment cmdEnv,
- List<String> arguments,
- Map<String, String> environment,
- OutErr outErr,
- int timeout,
- boolean allowNetwork,
- boolean sandboxDebug,
- boolean useFakeHostname,
- boolean useFakeUsername)
- throws ExecException {
- Command cmd;
- try {
- cmd =
- getCommand(
- cmdEnv,
- arguments,
- environment,
- timeout,
- allowNetwork,
- useFakeHostname,
- useFakeUsername);
- } catch (IOException e) {
- throw new UserExecException("I/O error during sandboxed execution", e);
- }
-
- TerminationStatus status = null;
- try {
- cmd.execute(
- /* stdin */ new byte[] {},
- getCommandObserver(timeout),
- outErr.getOutputStream(),
- outErr.getErrorStream(),
- /* killSubprocessOnInterrupt */ true);
- } catch (CommandException e) {
- boolean timedOut = false;
- if (e instanceof AbnormalTerminationException) {
- status = ((AbnormalTerminationException) e).getResult().getTerminationStatus();
- timedOut = !status.exited() && (status.getTerminatingSignal() == getSignalOnTimeout());
- }
-
- String statusMessage = status + " [sandboxed]";
-
- if (!verboseFailures) {
- // Simplest possible error message.
- throw new UserExecException(statusMessage, e, timedOut);
- }
-
- List<String> commandList = arguments;
- if (sandboxDebug) {
- // When using --sandbox_debug, show the entire command-line including the part where we call
- // the sandbox helper binary.
- commandList = Arrays.asList(cmd.getCommandLineElements());
- }
-
- String commandFailureMessage =
- CommandFailureUtils.describeCommandFailure(true, commandList, environment, null);
-
- if (!sandboxDebug) {
- commandFailureMessage += SANDBOX_DEBUG_SUGGESTION;
- }
-
- throw new UserExecException(commandFailureMessage, e, timedOut);
- }
- }
-
- /**
- * Returns the {@link Command} that the {@link #run} method will execute inside the sandbox.
- *
- * @param cmdEnv - the current command environment.
- * @param arguments - arguments of spawn to run inside the sandbox.
- * @param environment - environment variables to pass to the spawn.
- * @param timeout - after how many seconds should the process be killed.
- * @param allowNetwork - whether networking should be allowed for the process.
- * @param useFakeHostname - whether the hostname should be set to 'localhost' inside the sandbox.
- * @param useFakeUsername - whether the username should be set to 'nobody' inside the sandbox.
- */
- protected abstract Command getCommand(
- CommandEnvironment cmdEnv,
- List<String> arguments,
- Map<String, String> environment,
- int timeout,
- boolean allowNetwork,
- boolean useFakeHostname,
- boolean useFakeUsername)
- throws IOException;
-
- /**
- * Returns a {@link KillableObserver} that the {@link #run} method will use when executing the
- * command returned by {@link #getCommand}.
- */
- protected KillableObserver getCommandObserver(int timeout) {
- return Command.NO_OBSERVER;
- }
-
- /**
- * Returns the signal code that the command returned by {@link #getCommand} exits with in case of
- * a timeout.
- */
- protected int getSignalOnTimeout() {
- return 14; /* SIGALRM */
- }
-}
diff --git a/src/main/java/com/google/devtools/build/lib/sandbox/SandboxStrategy.java b/src/main/java/com/google/devtools/build/lib/sandbox/SandboxStrategy.java
index fe6a581fb2..513b8d4df8 100644
--- a/src/main/java/com/google/devtools/build/lib/sandbox/SandboxStrategy.java
+++ b/src/main/java/com/google/devtools/build/lib/sandbox/SandboxStrategy.java
@@ -14,11 +14,16 @@
package com.google.devtools.build.lib.sandbox;
+import static java.nio.charset.StandardCharsets.UTF_8;
+
import com.google.common.collect.ImmutableSet;
import com.google.common.eventbus.EventBus;
import com.google.devtools.build.lib.actions.ActionExecutionContext;
import com.google.devtools.build.lib.actions.ActionExecutionMetadata;
+import com.google.devtools.build.lib.actions.ActionInput;
+import com.google.devtools.build.lib.actions.ActionInputFileCache;
import com.google.devtools.build.lib.actions.ActionStatusMessage;
+import com.google.devtools.build.lib.actions.Artifact.ArtifactExpander;
import com.google.devtools.build.lib.actions.ExecException;
import com.google.devtools.build.lib.actions.ResourceManager;
import com.google.devtools.build.lib.actions.ResourceManager.ResourceHandle;
@@ -27,59 +32,57 @@ import com.google.devtools.build.lib.actions.Spawn;
import com.google.devtools.build.lib.actions.SpawnActionContext;
import com.google.devtools.build.lib.actions.Spawns;
import com.google.devtools.build.lib.actions.UserExecException;
-import com.google.devtools.build.lib.buildtool.BuildRequest;
-import com.google.devtools.build.lib.events.Event;
-import com.google.devtools.build.lib.events.EventHandler;
+import com.google.devtools.build.lib.exec.SpawnExecException;
+import com.google.devtools.build.lib.exec.SpawnInputExpander;
+import com.google.devtools.build.lib.exec.SpawnResult;
+import com.google.devtools.build.lib.exec.SpawnResult.Status;
+import com.google.devtools.build.lib.exec.SpawnRunner.ProgressStatus;
+import com.google.devtools.build.lib.exec.SpawnRunner.SpawnExecutionPolicy;
+import com.google.devtools.build.lib.rules.fileset.FilesetActionContext;
import com.google.devtools.build.lib.runtime.CommandEnvironment;
+import com.google.devtools.build.lib.shell.AbnormalTerminationException;
+import com.google.devtools.build.lib.shell.Command;
+import com.google.devtools.build.lib.shell.CommandException;
+import com.google.devtools.build.lib.shell.CommandResult;
+import com.google.devtools.build.lib.util.CommandFailureUtils;
+import com.google.devtools.build.lib.util.io.FileOutErr;
import com.google.devtools.build.lib.util.io.OutErr;
import com.google.devtools.build.lib.vfs.FileSystem;
import com.google.devtools.build.lib.vfs.Path;
import com.google.devtools.build.lib.vfs.PathFragment;
import java.io.IOException;
import java.util.Map;
-import java.util.Set;
+import java.util.SortedMap;
+import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;
/** Abstract common ancestor for sandbox strategies implementing the common parts. */
abstract class SandboxStrategy implements SandboxedSpawnActionContext {
+ private static final int LOCAL_EXEC_ERROR = -1;
+ private static final int POSIX_TIMEOUT_EXIT_CODE = /*SIGNAL_BASE=*/128 + /*SIGALRM=*/14;
+
+ private static final String SANDBOX_DEBUG_SUGGESTION =
+ "\n\nUse --sandbox_debug to see verbose messages from the sandbox";
- private final CommandEnvironment cmdEnv;
- private final BuildRequest buildRequest;
- private final Path execRoot;
private final Path sandboxBase;
private final boolean verboseFailures;
private final SandboxOptions sandboxOptions;
private final ImmutableSet<Path> inaccessiblePaths;
+ private final SpawnInputExpander spawnInputExpander;
public SandboxStrategy(
CommandEnvironment cmdEnv,
- BuildRequest buildRequest,
Path sandboxBase,
boolean verboseFailures,
SandboxOptions sandboxOptions) {
- this.cmdEnv = cmdEnv;
- this.buildRequest = buildRequest;
- this.execRoot = cmdEnv.getExecRoot();
this.sandboxBase = sandboxBase;
this.verboseFailures = verboseFailures;
this.sandboxOptions = sandboxOptions;
-
- ImmutableSet.Builder<Path> inaccessiblePaths = ImmutableSet.builder();
- FileSystem fileSystem = cmdEnv.getDirectories().getFileSystem();
- for (String path : sandboxOptions.sandboxBlockPath) {
- Path blockedPath = fileSystem.getPath(path);
- try {
- inaccessiblePaths.add(blockedPath.resolveSymbolicLinks());
- } catch (IOException e) {
- // It's OK to block access to an invalid symlink. In this case we'll just make the symlink
- // itself inaccessible, instead of the target, though.
- inaccessiblePaths.add(blockedPath);
- }
- }
- this.inaccessiblePaths = inaccessiblePaths.build();
+ this.inaccessiblePaths =
+ sandboxOptions.getInaccessiblePaths(cmdEnv.getDirectories().getFileSystem());
+ this.spawnInputExpander = new SpawnInputExpander(false);
}
- /** Executes the given {@code spawn}. */
@Override
public void exec(Spawn spawn, ActionExecutionContext actionExecutionContext)
throws ExecException, InterruptedException {
@@ -103,73 +106,168 @@ abstract class SandboxStrategy implements SandboxedSpawnActionContext {
eventBus.post(ActionStatusMessage.schedulingStrategy(owner));
try (ResourceHandle ignored =
ResourceManager.instance().acquireResources(owner, spawn.getLocalResources())) {
+ eventBus.post(ActionStatusMessage.runningStrategy(spawn.getResourceOwner(), getName()));
+ if (actionExecutionContext.reportsSubcommands()) {
+ actionExecutionContext.reportSubcommand(spawn);
+ }
actuallyExec(spawn, actionExecutionContext, writeOutputFiles);
} catch (IOException e) {
throw new UserExecException("I/O exception during sandboxed execution", e);
}
}
- protected abstract void actuallyExec(
+ private void actuallyExec(
Spawn spawn,
ActionExecutionContext actionExecutionContext,
AtomicReference<Class<? extends SpawnActionContext>> writeOutputFiles)
+ throws ExecException, IOException, InterruptedException {
+ final int timeoutSeconds = Spawns.getTimeoutSeconds(spawn);
+ SpawnExecutionPolicy policy = new SpawnExecutionPolicy() {
+ @Override
+ public ActionInputFileCache getActionInputFileCache() {
+ return actionExecutionContext.getActionInputFileCache();
+ }
+
+ @Override
+ public ArtifactExpander getArtifactExpander() {
+ return actionExecutionContext.getArtifactExpander();
+ }
+
+ @Override
+ public void lockOutputFiles() throws InterruptedException {
+ Class<? extends SpawnActionContext> token = SandboxStrategy.this.getClass();
+ if (writeOutputFiles != null
+ && writeOutputFiles.get() != token
+ && !writeOutputFiles.compareAndSet(null, token)) {
+ throw new InterruptedException();
+ }
+ }
+
+ @Override
+ public long getTimeoutMillis() {
+ return TimeUnit.SECONDS.toMillis(timeoutSeconds);
+ }
+
+ @Override
+ public FileOutErr getFileOutErr() {
+ return actionExecutionContext.getFileOutErr();
+ }
+
+ @Override
+ public SortedMap<PathFragment, ActionInput> getInputMapping() throws IOException {
+ return spawnInputExpander.getInputMapping(
+ spawn,
+ actionExecutionContext.getArtifactExpander(),
+ actionExecutionContext.getActionInputFileCache(),
+ actionExecutionContext.getContext(FilesetActionContext.class));
+ }
+
+ @Override
+ public void report(ProgressStatus state) {
+ // We already reported the progress, so do nothing here.
+ }
+ };
+ actuallyExec(spawn, policy);
+ }
+
+ protected abstract SpawnResult actuallyExec(Spawn spawn, SpawnExecutionPolicy policy)
throws ExecException, InterruptedException, IOException;
- protected void runSpawn(
- Spawn spawn,
- ActionExecutionContext actionExecutionContext,
- Map<String, String> spawnEnvironment,
- SandboxExecRoot sandboxExecRoot,
- Set<PathFragment> outputs,
- SandboxRunner runner,
- AtomicReference<Class<? extends SpawnActionContext>> writeOutputFiles)
- throws ExecException, InterruptedException {
- EventHandler eventHandler = actionExecutionContext.getEventHandler();
- ExecException execException = null;
- OutErr outErr = actionExecutionContext.getFileOutErr();
+ protected SpawnResult runSpawn(
+ Spawn originalSpawn,
+ SandboxedSpawn sandbox,
+ SpawnExecutionPolicy policy,
+ Path execRoot,
+ int timeoutSeconds)
+ throws ExecException, IOException, InterruptedException {
try {
- runner.run(
- cmdEnv,
- spawn.getArguments(),
- spawnEnvironment,
- outErr,
- Spawns.getTimeoutSeconds(spawn),
- SandboxHelpers.shouldAllowNetwork(buildRequest, spawn),
- sandboxOptions.sandboxDebug,
- sandboxOptions.sandboxFakeHostname,
- sandboxOptions.sandboxFakeUsername);
- } catch (ExecException e) {
- execException = e;
- }
+ sandbox.createFileSystem();
+ OutErr outErr = policy.getFileOutErr();
+ SpawnResult result = run(sandbox, outErr, timeoutSeconds);
+
+ policy.lockOutputFiles();
+
+ try {
+ // We copy the outputs even when the command failed.
+ sandbox.copyOutputs(execRoot);
+ } catch (IOException e) {
+ throw new IOException("Could not move output artifacts from sandboxed execution", e);
+ }
- if (writeOutputFiles != null && !writeOutputFiles.compareAndSet(null, SandboxStrategy.class)) {
- throw new InterruptedException();
+ if (result.status() != Status.SUCCESS || result.exitCode() != 0) {
+ String message;
+ if (sandboxOptions.sandboxDebug) {
+ message =
+ CommandFailureUtils.describeCommandFailure(
+ true, sandbox.getArguments(), sandbox.getEnvironment(), null);
+ } else {
+ message =
+ CommandFailureUtils.describeCommandFailure(
+ false, originalSpawn.getArguments(), originalSpawn.getEnvironment(), null)
+ + SANDBOX_DEBUG_SUGGESTION;
+ }
+ throw new SpawnExecException(
+ message, result, /*forciblyRunRemotely=*/false, /*catastrophe=*/false);
+ }
+ return result;
+ } finally {
+ if (!sandboxOptions.sandboxDebug) {
+ sandbox.delete();
+ }
}
+ }
+
+ private final SpawnResult run(SandboxedSpawn sandbox, OutErr outErr, int timeoutSeconds)
+ throws IOException, InterruptedException {
+ Command cmd = new Command(
+ sandbox.getArguments().toArray(new String[0]),
+ sandbox.getEnvironment(),
+ sandbox.getSandboxExecRoot().getPathFile());
+ long startTime = System.currentTimeMillis();
+ CommandResult result;
try {
- // We copy the outputs even when the command failed, otherwise StandaloneTestStrategy
- // won't be able to get the test logs of a failed test. (We should probably do this in
- // some better way.)
- sandboxExecRoot.copyOutputs(execRoot, outputs);
- } catch (IOException e) {
- if (execException == null) {
- throw new UserExecException("Could not move output artifacts from sandboxed execution", e);
- } else {
- // Catch the IOException and turn it into an error message, otherwise this might hide an
- // exception thrown during runner.run earlier.
- eventHandler.handle(
- Event.error(
- "I/O exception while extracting output artifacts from sandboxed execution: " + e));
+ result = cmd.execute(
+ /* stdin */ new byte[] {},
+ Command.NO_OBSERVER,
+ outErr.getOutputStream(),
+ outErr.getErrorStream(),
+ /* killSubprocessOnInterrupt */ true);
+ if (Thread.currentThread().isInterrupted()) {
+ throw new InterruptedException();
+ }
+ } catch (AbnormalTerminationException e) {
+ if (Thread.currentThread().isInterrupted()) {
+ throw new InterruptedException();
}
+ result = e.getResult();
+ } catch (CommandException e) {
+ // At the time this comment was written, this must be a ExecFailedException encapsulating an
+ // IOException from the underlying Subprocess.Factory.
+ String msg = e.getMessage() == null ? e.getClass().getName() : e.getMessage();
+ outErr.getErrorStream().write(("Action failed to execute: " + msg + "\n").getBytes(UTF_8));
+ outErr.getErrorStream().flush();
+ return new SpawnResult.Builder()
+ .setStatus(Status.EXECUTION_FAILED)
+ .setExitCode(LOCAL_EXEC_ERROR)
+ .build();
}
- if (execException != null) {
- outErr.printErr(
- "Use --strategy="
- + spawn.getMnemonic()
- + "=standalone to disable sandboxing for the failing actions.\n");
- throw execException;
- }
+ long wallTime = System.currentTimeMillis() - startTime;
+ boolean wasTimeout = wasTimeout(timeoutSeconds, wallTime);
+ Status status = wasTimeout ? Status.TIMEOUT : Status.SUCCESS;
+ int exitCode = status == Status.TIMEOUT
+ ? POSIX_TIMEOUT_EXIT_CODE
+ : result.getTerminationStatus().getRawExitCode();
+ return new SpawnResult.Builder()
+ .setStatus(status)
+ .setExitCode(exitCode)
+ .setWallTimeMillis(wallTime)
+ .build();
+ }
+
+ private boolean wasTimeout(int timeoutSeconds, long wallTimeMillis) {
+ return timeoutSeconds > 0 && wallTimeMillis / 1000.0 > timeoutSeconds;
}
/**
@@ -193,16 +291,23 @@ abstract class SandboxStrategy implements SandboxedSpawnActionContext {
// We have to make the TEST_TMPDIR directory writable if it is specified.
ImmutableSet.Builder<Path> writablePaths = ImmutableSet.builder();
writablePaths.add(sandboxExecRoot);
- if (env.containsKey("TEST_TMPDIR")) {
- // We add this even though it may be below sandboxExecRoot (and thus would already be writable
- // as a subpath) to take advantage of the side-effect that SymlinkedExecRoot also creates this
- // needed directory if it doesn't exist yet.
- writablePaths.add(sandboxExecRoot.getRelative(env.get("TEST_TMPDIR")));
+ String tmpDirString = env.get("TEST_TMPDIR");
+ if (tmpDirString != null) {
+ PathFragment testTmpDir = PathFragment.create(tmpDirString);
+ if (testTmpDir.isAbsolute()) {
+ writablePaths.add(sandboxExecRoot.getRelative(testTmpDir).resolveSymbolicLinks());
+ } else {
+ // We add this even though it may is below sandboxExecRoot (and thus would already be
+ // writable as a subpath) to take advantage of the side-effect that SymlinkedExecRoot also
+ // creates this needed directory if it doesn't exist yet.
+ writablePaths.add(sandboxExecRoot.getRelative(testTmpDir));
+ }
}
FileSystem fileSystem = sandboxExecRoot.getFileSystem();
for (String writablePath : sandboxOptions.sandboxWritablePath) {
- writablePaths.add(fileSystem.getPath(writablePath));
+ Path path = fileSystem.getPath(writablePath);
+ writablePaths.add(path.resolveSymbolicLinks());
}
return writablePaths.build();
@@ -212,6 +317,8 @@ abstract class SandboxStrategy implements SandboxedSpawnActionContext {
return inaccessiblePaths;
}
+ protected abstract String getName();
+
@Override
public String toString() {
return "sandboxed";
diff --git a/src/main/java/com/google/devtools/build/lib/sandbox/SandboxExecRoot.java b/src/main/java/com/google/devtools/build/lib/sandbox/SandboxedSpawn.java
index 0c392fd141..431a4b1d43 100644
--- a/src/main/java/com/google/devtools/build/lib/sandbox/SandboxExecRoot.java
+++ b/src/main/java/com/google/devtools/build/lib/sandbox/SandboxedSpawn.java
@@ -15,42 +15,45 @@
package com.google.devtools.build.lib.sandbox;
import com.google.devtools.build.lib.vfs.Path;
-import com.google.devtools.build.lib.vfs.PathFragment;
import java.io.IOException;
-import java.util.Collection;
+import java.util.List;
import java.util.Map;
-import java.util.Set;
/**
- * SandboxExecRoot is responsible for making a list of input files available inside the directory,
+ * A description of a subprocess, as well as the necessary file system / sandbox setup.
+ *
+ * <p>Instances are responsible for making a list of input files available inside the sandbox root,
* so that a process running inside the directory can access the files. It also handles moving the
* output files generated by the process out of the directory into a destination directory.
*/
-public interface SandboxExecRoot {
+interface SandboxedSpawn {
+ /** The path in which to execute the subprocess. */
+ Path getSandboxExecRoot();
+
+ /** The command-line of the subprocess. */
+ List<String> getArguments();
+
+ /** The environment variables to be set for the subprocess. */
+ Map<String, String> getEnvironment();
/**
* Creates the sandboxed execution root, making all {@code inputs} available for reading, making
* sure that the parent directories of all {@code outputs} and that all {@code writableDirs}
* exist and can be written into.
*
- * @param inputs Specifies the input files to be made available inside the directory. The key of
- * the map is a relative path inside the sandboxed execution root, while the value is the
- * absolute path of the file in the filesystem.
- * @param outputs Output files that the process is expected to write to as relative paths to the
- * sandboxed execution root.
- * @param writableDirs Directories that the process may write into. All paths that are not inside
- * the sandboxed execution root must be ignored by this method.
* @throws IOException
*/
- void createFileSystem(
- Map<PathFragment, Path> inputs, Collection<PathFragment> outputs, Set<Path> writableDirs)
- throws IOException;
+ void createFileSystem() throws IOException;
/**
* Moves all {@code outputs} to {@code execRoot} while keeping the directory structure.
*
* @throws IOException
*/
- void copyOutputs(Path execRoot, Collection<PathFragment> outputs) throws IOException;
+ void copyOutputs(Path execRoot) throws IOException;
+ /**
+ * Deletes the sandbox directory.
+ */
+ void delete();
}
diff --git a/src/main/java/com/google/devtools/build/lib/sandbox/SymlinkedExecRoot.java b/src/main/java/com/google/devtools/build/lib/sandbox/SymlinkedSandboxedSpawn.java
index a158f02961..96da98097e 100644
--- a/src/main/java/com/google/devtools/build/lib/sandbox/SymlinkedExecRoot.java
+++ b/src/main/java/com/google/devtools/build/lib/sandbox/SymlinkedSandboxedSpawn.java
@@ -24,6 +24,7 @@ import com.google.devtools.build.lib.vfs.Symlinks;
import java.io.IOException;
import java.util.Collection;
import java.util.HashSet;
+import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
@@ -32,18 +33,49 @@ import java.util.Set;
* Creates an execRoot for a Spawn that contains input files as symlinks to their original
* destination.
*/
-public final class SymlinkedExecRoot implements SandboxExecRoot {
-
+public class SymlinkedSandboxedSpawn implements SandboxedSpawn {
+ private final Path sandboxPath;
private final Path sandboxExecRoot;
+ private final List<String> arguments;
+ private final Map<String, String> environment;
+ private final Map<PathFragment, Path> inputs;
+ private final Collection<PathFragment> outputs;
+ private final Set<Path> writableDirs;
- public SymlinkedExecRoot(Path sandboxExecRoot) {
+ public SymlinkedSandboxedSpawn(
+ Path sandboxPath,
+ Path sandboxExecRoot,
+ List<String> arguments,
+ Map<String, String> environment,
+ Map<PathFragment, Path> inputs,
+ Collection<PathFragment> outputs,
+ Set<Path> writableDirs) {
+ this.sandboxPath = sandboxPath;
this.sandboxExecRoot = sandboxExecRoot;
+ this.arguments = arguments;
+ this.environment = environment;
+ this.inputs = inputs;
+ this.outputs = outputs;
+ this.writableDirs = writableDirs;
}
@Override
- public void createFileSystem(
- Map<PathFragment, Path> inputs, Collection<PathFragment> outputs, Set<Path> writableDirs)
- throws IOException {
+ public Path getSandboxExecRoot() {
+ return sandboxExecRoot;
+ }
+
+ @Override
+ public List<String> getArguments() {
+ return arguments;
+ }
+
+ @Override
+ public Map<String, String> getEnvironment() {
+ return environment;
+ }
+
+ @Override
+ public void createFileSystem() throws IOException {
Set<Path> createdDirs = new HashSet<>();
cleanFileSystem(inputs.keySet());
FileSystemUtils.createDirectoryAndParentsWithCache(createdDirs, sandboxExecRoot);
@@ -140,7 +172,7 @@ public final class SymlinkedExecRoot implements SandboxExecRoot {
/** Moves all {@code outputs} to {@code execRoot}. */
@Override
- public void copyOutputs(Path execRoot, Collection<PathFragment> outputs) throws IOException {
+ public void copyOutputs(Path execRoot) throws IOException {
for (PathFragment output : outputs) {
Path source = sandboxExecRoot.getRelative(output);
Path target = execRoot.getRelative(output);
@@ -157,4 +189,19 @@ public final class SymlinkedExecRoot implements SandboxExecRoot {
}
}
}
+
+ @Override
+ public void delete() {
+ try {
+ FileSystemUtils.deleteTree(sandboxPath);
+ } catch (IOException e) {
+ // This usually means that the Spawn itself exited, but still has children running that
+ // we couldn't wait for, which now block deletion of the sandbox directory. On Linux this
+ // should never happen, as we use PID namespaces and where they are not available the
+ // subreaper feature to make sure all children have been reliably killed before returning,
+ // but on other OS this might not always work. The SandboxModule will try to delete them
+ // again when the build is all done, at which point it hopefully works, so let's just go
+ // on here.
+ }
+ }
}
diff --git a/src/main/java/com/google/devtools/build/lib/standalone/StandaloneSpawnStrategy.java b/src/main/java/com/google/devtools/build/lib/standalone/StandaloneSpawnStrategy.java
index 886fa45a33..b1e0e9d93b 100644
--- a/src/main/java/com/google/devtools/build/lib/standalone/StandaloneSpawnStrategy.java
+++ b/src/main/java/com/google/devtools/build/lib/standalone/StandaloneSpawnStrategy.java
@@ -18,6 +18,7 @@ import com.google.devtools.build.lib.actions.ActionExecutionContext;
import com.google.devtools.build.lib.actions.ActionInput;
import com.google.devtools.build.lib.actions.ActionInputFileCache;
import com.google.devtools.build.lib.actions.ActionStatusMessage;
+import com.google.devtools.build.lib.actions.Artifact.ArtifactExpander;
import com.google.devtools.build.lib.actions.ExecException;
import com.google.devtools.build.lib.actions.ExecutionStrategy;
import com.google.devtools.build.lib.actions.ResourceManager;
@@ -85,6 +86,11 @@ public class StandaloneSpawnStrategy implements SpawnActionContext {
}
@Override
+ public ArtifactExpander getArtifactExpander() {
+ return actionExecutionContext.getArtifactExpander();
+ }
+
+ @Override
public void lockOutputFiles() throws InterruptedException {
// Do nothing for now.
}
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 7192c3e029..a06ddc0114 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
@@ -14,8 +14,10 @@
package com.google.devtools.build.lib.worker;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
-import com.google.devtools.build.lib.sandbox.SymlinkedExecRoot;
+import com.google.devtools.build.lib.sandbox.SymlinkedSandboxedSpawn;
import com.google.devtools.build.lib.vfs.FileSystemUtils;
import com.google.devtools.build.lib.vfs.Path;
import java.io.IOException;
@@ -23,30 +25,46 @@ import java.io.IOException;
/** A {@link Worker} that runs inside a sandboxed execution root. */
final class SandboxedWorker extends Worker {
private final Path workDir;
- private final SymlinkedExecRoot symlinkedExecRoot;
SandboxedWorker(WorkerKey workerKey, int workerId, Path workDir, Path logFile) {
super(workerKey, workerId, workDir, logFile);
this.workDir = workDir;
- this.symlinkedExecRoot = new SymlinkedExecRoot(workDir);
}
@Override
void destroy() throws IOException {
super.destroy();
- if (symlinkedExecRoot != null) {
- FileSystemUtils.deleteTree(workDir);
- }
+ FileSystemUtils.deleteTree(workDir);
}
@Override
public void prepareExecution(WorkerKey key) throws IOException {
- symlinkedExecRoot.createFileSystem(
- key.getInputFiles(), key.getOutputFiles(), ImmutableSet.<Path>of());
+ // Note: the key passed in here may be different from the key passed to the constructor for
+ // subsequent invocations of the same worker.
+ // TODO(ulfjack): Remove WorkerKey.getInputFiles and WorkerKey.getOutputFiles; they are only
+ // used to pass information to this method and the method below. Instead, don't pass the
+ // WorkerKey to this method but only the input and output files.
+ new SymlinkedSandboxedSpawn(
+ workDir,
+ workDir,
+ ImmutableList.of("/does_not_exist"),
+ ImmutableMap.<String, String>of(),
+ key.getInputFiles(),
+ key.getOutputFiles(),
+ ImmutableSet.<Path>of()).createFileSystem();
}
@Override
public void finishExecution(WorkerKey key) throws IOException {
- symlinkedExecRoot.copyOutputs(key.getExecRoot(), key.getOutputFiles());
+ // Note: the key passed in here may be different from the key passed to the constructor for
+ // subsequent invocations of the same worker.
+ new SymlinkedSandboxedSpawn(
+ workDir,
+ workDir,
+ ImmutableList.of("/does_not_exist"),
+ ImmutableMap.<String, String>of(),
+ key.getInputFiles(),
+ key.getOutputFiles(),
+ ImmutableSet.<Path>of()).copyOutputs(key.getExecRoot());
}
}