aboutsummaryrefslogtreecommitdiffhomepage
path: root/src/main/java/com/google/devtools
diff options
context:
space:
mode:
authorGravatar ulfjack <ulfjack@google.com>2017-07-12 16:51:20 +0200
committerGravatar László Csomor <laszlocsomor@google.com>2017-07-13 09:49:19 +0200
commita63da8e99b19a5cdd103998f073fab57e883b82e (patch)
tree59aed5039018055b66a3e67576193f27865d4c64 /src/main/java/com/google/devtools
parent67bd6fc6354f2abbbc149fcd0120228b538842d3 (diff)
Split the sandbox strategies into SpawnRunner implementations and strategies
This adds a bunch of classes that only implement the SpawnRunner interface, and will allow us to support remote caching in combination with local sandboxed execution in a subsequent change. PiperOrigin-RevId: 161664556
Diffstat (limited to 'src/main/java/com/google/devtools')
-rw-r--r--src/main/java/com/google/devtools/build/lib/sandbox/AbstractSandboxSpawnRunner.java228
-rw-r--r--src/main/java/com/google/devtools/build/lib/sandbox/DarwinSandboxedSpawnRunner.java272
-rw-r--r--src/main/java/com/google/devtools/build/lib/sandbox/DarwinSandboxedStrategy.java262
-rw-r--r--src/main/java/com/google/devtools/build/lib/sandbox/LinuxSandboxedSpawnRunner.java309
-rw-r--r--src/main/java/com/google/devtools/build/lib/sandbox/LinuxSandboxedStrategy.java284
-rw-r--r--src/main/java/com/google/devtools/build/lib/sandbox/ProcessWrapperSandboxedSpawnRunner.java88
-rw-r--r--src/main/java/com/google/devtools/build/lib/sandbox/ProcessWrapperSandboxedStrategy.java64
-rw-r--r--src/main/java/com/google/devtools/build/lib/sandbox/SandboxActionContextConsumer.java6
-rw-r--r--src/main/java/com/google/devtools/build/lib/sandbox/SandboxActionContextProvider.java8
-rw-r--r--src/main/java/com/google/devtools/build/lib/sandbox/SandboxStrategy.java226
10 files changed, 946 insertions, 801 deletions
diff --git a/src/main/java/com/google/devtools/build/lib/sandbox/AbstractSandboxSpawnRunner.java b/src/main/java/com/google/devtools/build/lib/sandbox/AbstractSandboxSpawnRunner.java
new file mode 100644
index 0000000000..d1b6e98452
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/sandbox/AbstractSandboxSpawnRunner.java
@@ -0,0 +1,228 @@
+// 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 static java.nio.charset.StandardCharsets.UTF_8;
+
+import com.google.common.collect.ImmutableSet;
+import com.google.devtools.build.lib.actions.ActionExecutionMetadata;
+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;
+import com.google.devtools.build.lib.actions.Spawn;
+import com.google.devtools.build.lib.actions.UserExecException;
+import com.google.devtools.build.lib.exec.SpawnExecException;
+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;
+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.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;
+
+/** Abstract common ancestor for sandbox spawn runners implementing the common parts. */
+abstract class AbstractSandboxSpawnRunner implements SpawnRunner {
+ 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 Path sandboxBase;
+ private final SandboxOptions sandboxOptions;
+ private final ImmutableSet<Path> inaccessiblePaths;
+
+ public AbstractSandboxSpawnRunner(
+ CommandEnvironment cmdEnv,
+ Path sandboxBase,
+ SandboxOptions sandboxOptions) {
+ this.sandboxBase = sandboxBase;
+ this.sandboxOptions = sandboxOptions;
+ this.inaccessiblePaths =
+ sandboxOptions.getInaccessiblePaths(cmdEnv.getDirectories().getFileSystem());
+ }
+
+ @Override
+ public SpawnResult exec(Spawn spawn, SpawnExecutionPolicy policy)
+ throws ExecException, InterruptedException {
+ ActionExecutionMetadata owner = spawn.getResourceOwner();
+ policy.report(ProgressStatus.SCHEDULING);
+ try (ResourceHandle ignored =
+ ResourceManager.instance().acquireResources(owner, spawn.getLocalResources())) {
+ policy.report(ProgressStatus.EXECUTING);
+ return actuallyExec(spawn, policy);
+ } catch (IOException e) {
+ throw new UserExecException("I/O exception during sandboxed execution", e);
+ }
+ }
+
+ protected abstract SpawnResult actuallyExec(Spawn spawn, SpawnExecutionPolicy policy)
+ throws ExecException, InterruptedException, IOException;
+
+ protected SpawnResult runSpawn(
+ Spawn originalSpawn,
+ SandboxedSpawn sandbox,
+ SpawnExecutionPolicy policy,
+ Path execRoot,
+ int timeoutSeconds)
+ throws ExecException, IOException, InterruptedException {
+ try {
+ 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 (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 {
+ 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();
+ }
+
+ 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;
+ }
+
+ /**
+ * Returns a temporary directory that should be used as the sandbox directory for a single action.
+ */
+ protected Path getSandboxRoot() throws IOException {
+ return sandboxBase.getRelative(
+ java.nio.file.Files.createTempDirectory(
+ java.nio.file.Paths.get(sandboxBase.getPathString()), "")
+ .getFileName()
+ .toString());
+ }
+
+ /**
+ * Gets the list of directories that the spawn will assume to be writable.
+ *
+ * @throws IOException because we might resolve symlinks, which throws {@link IOException}.
+ */
+ protected ImmutableSet<Path> getWritableDirs(Path sandboxExecRoot, Map<String, String> env)
+ throws IOException {
+ // We have to make the TEST_TMPDIR directory writable if it is specified.
+ ImmutableSet.Builder<Path> writablePaths = ImmutableSet.builder();
+ writablePaths.add(sandboxExecRoot);
+ 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 is below sandboxExecRoot (and thus already 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) {
+ Path path = fileSystem.getPath(writablePath);
+ writablePaths.add(path);
+ writablePaths.add(path.resolveSymbolicLinks());
+ }
+
+ return writablePaths.build();
+ }
+
+ protected ImmutableSet<Path> getInaccessiblePaths() {
+ return inaccessiblePaths;
+ }
+
+ protected abstract String getName();
+}
diff --git a/src/main/java/com/google/devtools/build/lib/sandbox/DarwinSandboxedSpawnRunner.java b/src/main/java/com/google/devtools/build/lib/sandbox/DarwinSandboxedSpawnRunner.java
new file mode 100644
index 0000000000..446daa1c94
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/sandbox/DarwinSandboxedSpawnRunner.java
@@ -0,0 +1,272 @@
+// 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.collect.ImmutableSet;
+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.SpawnResult;
+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.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.OS;
+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.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.TimeUnit;
+
+/** Spawn runner that uses Darwin (macOS) sandboxing to execute a process. */
+@ExecutionStrategy(
+ name = {"sandboxed", "darwin-sandbox"},
+ contextType = SpawnActionContext.class
+)
+final class DarwinSandboxedSpawnRunner extends AbstractSandboxSpawnRunner {
+ private static final String SANDBOX_EXEC = "/usr/bin/sandbox-exec";
+
+ public static boolean isSupported(CommandEnvironment 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 allowNetwork;
+ private final String productName;
+ private final Path processWrapper;
+
+ /**
+ * The set of directories that always should be writable, independent of the Spawn itself.
+ *
+ * <p>We cache this, because creating it involves executing {@code getconf}, which is expensive.
+ */
+ private final ImmutableSet<Path> alwaysWritableDirs;
+ private final LocalEnvProvider localEnvProvider;
+
+ DarwinSandboxedSpawnRunner(
+ CommandEnvironment cmdEnv,
+ BuildRequest buildRequest,
+ Path sandboxBase,
+ String productName) throws IOException {
+ super(
+ cmdEnv,
+ sandboxBase,
+ buildRequest.getOptions(SandboxOptions.class));
+ this.execRoot = cmdEnv.getExecRoot();
+ this.allowNetwork = SandboxHelpers.shouldAllowNetwork(cmdEnv.getOptions());
+ this.productName = productName;
+ this.alwaysWritableDirs = getAlwaysWritableDirs(cmdEnv.getDirectories().getFileSystem());
+ this.processWrapper = ProcessWrapperRunner.getProcessWrapper(cmdEnv);
+ this.localEnvProvider = new XCodeLocalEnvProvider();
+ }
+
+ private static void addPathToSetIfExists(FileSystem fs, Set<Path> paths, String path)
+ throws IOException {
+ if (path != null) {
+ addPathToSetIfExists(paths, fs.getPath(path));
+ }
+ }
+
+ private static void addPathToSetIfExists(Set<Path> paths, Path path) throws IOException {
+ if (path.exists()) {
+ paths.add(path.resolveSymbolicLinks());
+ }
+ }
+
+ private static ImmutableSet<Path> getAlwaysWritableDirs(FileSystem fs) throws IOException {
+ HashSet<Path> writableDirs = new HashSet<>();
+
+ addPathToSetIfExists(fs, writableDirs, "/dev");
+ addPathToSetIfExists(fs, writableDirs, System.getenv("TMPDIR"));
+ addPathToSetIfExists(fs, writableDirs, "/tmp");
+ addPathToSetIfExists(fs, writableDirs, "/private/tmp");
+ addPathToSetIfExists(fs, writableDirs, "/private/var/tmp");
+
+ // On macOS, in addition to what is specified in $TMPDIR, two other temporary directories may be
+ // written to by processes. We have to get their location by calling "getconf".
+ addPathToSetIfExists(fs, writableDirs, getConfStr("DARWIN_USER_TEMP_DIR"));
+ addPathToSetIfExists(fs, writableDirs, getConfStr("DARWIN_USER_CACHE_DIR"));
+
+ // ~/Library/Cache and ~/Library/Logs need to be writable (cf. issue #2231).
+ Path homeDir = fs.getPath(System.getProperty("user.home"));
+ addPathToSetIfExists(writableDirs, homeDir.getRelative("Library/Cache"));
+ addPathToSetIfExists(writableDirs, homeDir.getRelative("Library/Logs"));
+
+ // Certain Xcode tools expect to be able to write to this path.
+ addPathToSetIfExists(writableDirs, homeDir.getRelative("Library/Developer"));
+
+ return ImmutableSet.copyOf(writableDirs);
+ }
+
+ /**
+ * Returns the value of a POSIX or X/Open system configuration variable.
+ */
+ private static String getConfStr(String confVar) throws IOException {
+ String[] commandArr = new String[2];
+ commandArr[0] = "/usr/bin/getconf";
+ commandArr[1] = confVar;
+ Command cmd = new Command(commandArr);
+ CommandResult res;
+ try {
+ res = cmd.execute();
+ } catch (CommandException e) {
+ throw new IOException("getconf failed", e);
+ }
+ return new String(res.getStdout(), UTF_8).trim();
+ }
+
+ @Override
+ 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 =
+ localEnvProvider.rewriteLocalEnv(spawn.getEnvironment(), execRoot, productName);
+
+ final HashSet<Path> writableDirs = new HashSet<>(alwaysWritableDirs);
+ ImmutableSet<Path> extraWritableDirs = getWritableDirs(sandboxExecRoot, spawnEnvironment);
+ writableDirs.addAll(extraWritableDirs);
+
+ ImmutableSet<PathFragment> outputs = SandboxHelpers.getOutputFiles(spawn);
+
+ 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) {
+ @Override
+ public void createFileSystem() throws IOException {
+ super.createFileSystem();
+ writeConfig(
+ sandboxConfigPath, writableDirs, getInaccessiblePaths(), allowNetworkForThisSpawn);
+ }
+ };
+ return runSpawn(spawn, sandbox, policy, execRoot, timeoutSeconds);
+ }
+
+ 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;
+ }
+
+ 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/DarwinSandboxedStrategy.java b/src/main/java/com/google/devtools/build/lib/sandbox/DarwinSandboxedStrategy.java
index 89b961edf9..38c8645f2c 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
@@ -14,279 +14,29 @@
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.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.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.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.OS;
-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.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.TimeUnit;
/** Strategy that uses sandboxing to execute a process, for Darwin */
+//TODO(ulfjack): This class only exists for this annotation. Find a better way to handle this!
@ExecutionStrategy(
name = {"sandboxed", "darwin-sandbox"},
contextType = SpawnActionContext.class
)
-public class DarwinSandboxedStrategy extends SandboxStrategy {
- private static final String SANDBOX_EXEC = "/usr/bin/sandbox-exec";
-
- public static boolean isSupported(CommandEnvironment 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 allowNetwork;
- private final String productName;
- private final Path processWrapper;
-
- /**
- * The set of directories that always should be writable, independent of the Spawn itself.
- *
- * <p>We cache this, because creating it involves executing {@code getconf}, which is expensive.
- */
- private final ImmutableSet<Path> alwaysWritableDirs;
- private final LocalEnvProvider localEnvProvider;
-
- private DarwinSandboxedStrategy(
+final class DarwinSandboxedStrategy extends SandboxStrategy {
+ DarwinSandboxedStrategy(
CommandEnvironment cmdEnv,
BuildRequest buildRequest,
Path sandboxBase,
boolean verboseFailures,
- String productName,
- ImmutableSet<Path> alwaysWritableDirs) {
+ String productName) throws IOException {
super(
- cmdEnv,
- sandboxBase,
- verboseFailures,
- buildRequest.getOptions(SandboxOptions.class));
- this.execRoot = cmdEnv.getExecRoot();
- this.allowNetwork = SandboxHelpers.shouldAllowNetwork(cmdEnv.getOptions());
- this.productName = productName;
- this.alwaysWritableDirs = alwaysWritableDirs;
- this.processWrapper = ProcessWrapperRunner.getProcessWrapper(cmdEnv);
- this.localEnvProvider = new XCodeLocalEnvProvider();
- }
-
- public static DarwinSandboxedStrategy create(
- CommandEnvironment cmdEnv,
- BuildRequest buildRequest,
- Path sandboxBase,
- boolean verboseFailures,
- String productName)
- throws IOException {
- return new DarwinSandboxedStrategy(
- cmdEnv,
- buildRequest,
- sandboxBase,
verboseFailures,
- productName,
- getAlwaysWritableDirs(cmdEnv.getDirectories().getFileSystem()));
- }
-
- private static void addPathToSetIfExists(FileSystem fs, Set<Path> paths, String path)
- throws IOException {
- if (path != null) {
- addPathToSetIfExists(paths, fs.getPath(path));
- }
- }
-
- private static void addPathToSetIfExists(Set<Path> paths, Path path) throws IOException {
- if (path.exists()) {
- paths.add(path.resolveSymbolicLinks());
- }
- }
-
- private static ImmutableSet<Path> getAlwaysWritableDirs(FileSystem fs) throws IOException {
- HashSet<Path> writableDirs = new HashSet<>();
-
- addPathToSetIfExists(fs, writableDirs, "/dev");
- addPathToSetIfExists(fs, writableDirs, System.getenv("TMPDIR"));
- addPathToSetIfExists(fs, writableDirs, "/tmp");
- addPathToSetIfExists(fs, writableDirs, "/private/tmp");
- addPathToSetIfExists(fs, writableDirs, "/private/var/tmp");
-
- // On macOS, in addition to what is specified in $TMPDIR, two other temporary directories may be
- // written to by processes. We have to get their location by calling "getconf".
- addPathToSetIfExists(fs, writableDirs, getConfStr("DARWIN_USER_TEMP_DIR"));
- addPathToSetIfExists(fs, writableDirs, getConfStr("DARWIN_USER_CACHE_DIR"));
-
- // ~/Library/Cache and ~/Library/Logs need to be writable (cf. issue #2231).
- Path homeDir = fs.getPath(System.getProperty("user.home"));
- addPathToSetIfExists(writableDirs, homeDir.getRelative("Library/Cache"));
- addPathToSetIfExists(writableDirs, homeDir.getRelative("Library/Logs"));
-
- // Certain Xcode tools expect to be able to write to this path.
- addPathToSetIfExists(writableDirs, homeDir.getRelative("Library/Developer"));
-
- return ImmutableSet.copyOf(writableDirs);
- }
-
- /**
- * Returns the value of a POSIX or X/Open system configuration variable.
- */
- private static String getConfStr(String confVar) throws IOException {
- String[] commandArr = new String[2];
- commandArr[0] = "/usr/bin/getconf";
- commandArr[1] = confVar;
- Command cmd = new Command(commandArr);
- CommandResult res;
- try {
- res = cmd.execute();
- } catch (CommandException e) {
- throw new IOException("getconf failed", e);
- }
- return new String(res.getStdout(), UTF_8).trim();
- }
-
- @Override
- 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 =
- localEnvProvider.rewriteLocalEnv(spawn.getEnvironment(), execRoot, productName);
-
- final HashSet<Path> writableDirs = new HashSet<>(alwaysWritableDirs);
- ImmutableSet<Path> extraWritableDirs = getWritableDirs(sandboxExecRoot, spawnEnvironment);
- writableDirs.addAll(extraWritableDirs);
-
- ImmutableSet<PathFragment> outputs = SandboxHelpers.getOutputFiles(spawn);
-
- 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) {
- @Override
- public void createFileSystem() throws IOException {
- super.createFileSystem();
- writeConfig(
- sandboxConfigPath, writableDirs, getInaccessiblePaths(), allowNetworkForThisSpawn);
- }
- };
- return runSpawn(spawn, sandbox, policy, execRoot, timeoutSeconds);
- }
-
- 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;
- }
-
- 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";
+ buildRequest.getOptions(SandboxOptions.class),
+ new DarwinSandboxedSpawnRunner(cmdEnv, buildRequest, sandboxBase, productName));
}
}
diff --git a/src/main/java/com/google/devtools/build/lib/sandbox/LinuxSandboxedSpawnRunner.java b/src/main/java/com/google/devtools/build/lib/sandbox/LinuxSandboxedSpawnRunner.java
new file mode 100644
index 0000000000..0730fc6934
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/sandbox/LinuxSandboxedSpawnRunner.java
@@ -0,0 +1,309 @@
+// 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.collect.ImmutableSet;
+import com.google.common.collect.Maps;
+import com.google.common.io.ByteStreams;
+import com.google.devtools.build.lib.actions.ExecException;
+import com.google.devtools.build.lib.actions.Spawn;
+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.SpawnResult;
+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.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.TimeUnit;
+
+/** Spawn runner that uses linux sandboxing APIs to execute a local subprocess. */
+final class LinuxSandboxedSpawnRunner extends AbstractSandboxSpawnRunner {
+ private static final String LINUX_SANDBOX = "linux-sandbox";
+
+ public static boolean isSupported(CommandEnvironment 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 allowNetwork;
+ private final Path linuxSandbox;
+ private final Path inaccessibleHelperFile;
+ private final Path inaccessibleHelperDir;
+
+ LinuxSandboxedSpawnRunner(
+ CommandEnvironment cmdEnv,
+ BuildRequest buildRequest,
+ Path sandboxBase,
+ Path inaccessibleHelperFile,
+ Path inaccessibleHelperDir) {
+ super(
+ cmdEnv,
+ sandboxBase,
+ buildRequest.getOptions(SandboxOptions.class));
+ this.sandboxOptions = cmdEnv.getOptions().getOptions(SandboxOptions.class);
+ this.blazeDirs = cmdEnv.getDirectories();
+ this.execRoot = cmdEnv.getExecRoot();
+ this.allowNetwork = SandboxHelpers.shouldAllowNetwork(cmdEnv.getOptions());
+ this.linuxSandbox = getLinuxSandbox(cmdEnv);
+ this.inaccessibleHelperFile = inaccessibleHelperFile;
+ this.inaccessibleHelperDir = inaccessibleHelperDir;
+ }
+
+ @Override
+ protected SpawnResult actuallyExec(Spawn spawn, SpawnExecutionPolicy policy)
+ throws IOException, ExecException, InterruptedException {
+ // 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());
+ ImmutableSet<PathFragment> outputs = SandboxHelpers.getOutputFiles(spawn);
+ 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);
+ }
+
+ 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());
+
+ 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
+ protected ImmutableSet<Path> getWritableDirs(Path sandboxExecRoot, Map<String, String> env)
+ throws IOException {
+ ImmutableSet.Builder<Path> writableDirs = ImmutableSet.builder();
+ writableDirs.addAll(super.getWritableDirs(sandboxExecRoot, env));
+
+ FileSystem fs = sandboxExecRoot.getFileSystem();
+ writableDirs.add(fs.getPath("/dev/shm").resolveSymbolicLinks());
+ writableDirs.add(fs.getPath("/tmp"));
+
+ return writableDirs.build();
+ }
+
+ private ImmutableSet<Path> getTmpfsPaths() {
+ ImmutableSet.Builder<Path> tmpfsPaths = ImmutableSet.builder();
+ for (String tmpfsPath : sandboxOptions.sandboxTmpfsPath) {
+ tmpfsPaths.add(blazeDirs.getFileSystem().getPath(tmpfsPath));
+ }
+ return tmpfsPaths.build();
+ }
+
+ private SortedMap<Path, Path> getReadOnlyBindMounts(
+ BlazeDirectories blazeDirs, Path sandboxExecRoot) throws UserExecException {
+ Path tmpPath = blazeDirs.getFileSystem().getPath("/tmp");
+ final SortedMap<Path, Path> bindMounts = Maps.newTreeMap();
+ if (blazeDirs.getWorkspace().startsWith(tmpPath)) {
+ bindMounts.put(blazeDirs.getWorkspace(), blazeDirs.getWorkspace());
+ }
+ if (blazeDirs.getOutputBase().startsWith(tmpPath)) {
+ bindMounts.put(blazeDirs.getOutputBase(), blazeDirs.getOutputBase());
+ }
+ for (ImmutableMap.Entry<String, String> additionalMountPath :
+ sandboxOptions.sandboxAdditionalMounts) {
+ try {
+ final Path mountTarget = blazeDirs.getFileSystem().getPath(additionalMountPath.getValue());
+ // If source path is relative, treat it as a relative path inside the execution root
+ final Path mountSource = sandboxExecRoot.getRelative(additionalMountPath.getKey());
+ // If a target has more than one source path, the latter one will take effect.
+ bindMounts.put(mountTarget, mountSource);
+ } catch (IllegalArgumentException e) {
+ throw new UserExecException(
+ String.format("Error occurred when analyzing bind mount pairs. %s", e.getMessage()));
+ }
+ }
+ for (Path inaccessiblePath : getInaccessiblePaths()) {
+ if (inaccessiblePath.isDirectory(Symlinks.NOFOLLOW)) {
+ bindMounts.put(inaccessiblePath, inaccessibleHelperDir);
+ } else {
+ bindMounts.put(inaccessiblePath, inaccessibleHelperFile);
+ }
+ }
+ validateBindMounts(bindMounts);
+ return bindMounts;
+ }
+
+ /**
+ * This method does the following things: - If mount source does not exist on the host system,
+ * throw an error message - If mount target exists, check whether the source and target are of the
+ * same type - If mount target does not exist on the host system, throw an error message
+ *
+ * @param bindMounts the bind mounts map with target as key and source as value
+ * @throws UserExecException
+ */
+ private void validateBindMounts(SortedMap<Path, Path> bindMounts) throws UserExecException {
+ for (SortedMap.Entry<Path, Path> bindMount : bindMounts.entrySet()) {
+ final Path source = bindMount.getValue();
+ final Path target = bindMount.getKey();
+ // Mount source should exist in the file system
+ if (!source.exists()) {
+ throw new UserExecException(String.format("Mount source '%s' does not exist.", source));
+ }
+ // If target exists, but is not of the same type as the source, then we cannot mount it.
+ if (target.exists()) {
+ boolean areBothDirectories = source.isDirectory() && target.isDirectory();
+ boolean isSourceFile = source.isFile() || source.isSymbolicLink();
+ boolean isTargetFile = target.isFile() || target.isSymbolicLink();
+ boolean areBothFiles = isSourceFile && isTargetFile;
+ if (!(areBothDirectories || areBothFiles)) {
+ // Source and target are not of the same type; we cannot mount it.
+ throw new UserExecException(
+ String.format(
+ "Mount target '%s' is not of the same type as mount source '%s'.",
+ target, source));
+ }
+ } else {
+ // Mount target should exist in the file system
+ throw new UserExecException(
+ String.format(
+ "Mount target '%s' does not exist. Bazel only supports bind mounting on top of "
+ + "existing files/directories. Please create an empty file or directory at "
+ + "the mount target path according to the type of mount source.",
+ target));
+ }
+ }
+ }
+}
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 f0d3031e66..3d556d3bfc 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
@@ -14,94 +14,21 @@
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.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.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.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.TimeUnit;
/** Strategy that uses sandboxing to execute a process. */
+// TODO(ulfjack): This class only exists for this annotation. Find a better way to handle this!
@ExecutionStrategy(
name = {"sandboxed", "linux-sandbox"},
contextType = SpawnActionContext.class
)
-public class LinuxSandboxedStrategy extends SandboxStrategy {
- private static final String LINUX_SANDBOX = "linux-sandbox";
-
- public static boolean isSupported(CommandEnvironment 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 allowNetwork;
- private final Path linuxSandbox;
- private final Path inaccessibleHelperFile;
- private final Path inaccessibleHelperDir;
-
+public final class LinuxSandboxedStrategy extends SandboxStrategy {
private LinuxSandboxedStrategy(
CommandEnvironment cmdEnv,
BuildRequest buildRequest,
@@ -110,17 +37,10 @@ public class LinuxSandboxedStrategy extends SandboxStrategy {
Path inaccessibleHelperFile,
Path inaccessibleHelperDir) {
super(
- cmdEnv,
- sandboxBase,
verboseFailures,
- buildRequest.getOptions(SandboxOptions.class));
- this.sandboxOptions = cmdEnv.getOptions().getOptions(SandboxOptions.class);
- this.blazeDirs = cmdEnv.getDirectories();
- this.execRoot = cmdEnv.getExecRoot();
- this.allowNetwork = SandboxHelpers.shouldAllowNetwork(cmdEnv.getOptions());
- this.linuxSandbox = getLinuxSandbox(cmdEnv);
- this.inaccessibleHelperFile = inaccessibleHelperFile;
- this.inaccessibleHelperDir = inaccessibleHelperDir;
+ buildRequest.getOptions(SandboxOptions.class),
+ new LinuxSandboxedSpawnRunner(
+ cmdEnv, buildRequest, sandboxBase, inaccessibleHelperFile, inaccessibleHelperDir));
}
static LinuxSandboxedStrategy create(
@@ -149,198 +69,4 @@ public class LinuxSandboxedStrategy extends SandboxStrategy {
inaccessibleHelperFile,
inaccessibleHelperDir);
}
-
- @Override
- protected SpawnResult actuallyExec(Spawn spawn, SpawnExecutionPolicy policy)
- throws IOException, ExecException, InterruptedException {
- // 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());
- ImmutableSet<PathFragment> outputs = SandboxHelpers.getOutputFiles(spawn);
- 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);
- }
-
- 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());
-
- 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
- protected ImmutableSet<Path> getWritableDirs(Path sandboxExecRoot, Map<String, String> env)
- throws IOException {
- ImmutableSet.Builder<Path> writableDirs = ImmutableSet.builder();
- writableDirs.addAll(super.getWritableDirs(sandboxExecRoot, env));
-
- FileSystem fs = sandboxExecRoot.getFileSystem();
- writableDirs.add(fs.getPath("/dev/shm").resolveSymbolicLinks());
- writableDirs.add(fs.getPath("/tmp"));
-
- return writableDirs.build();
- }
-
- private ImmutableSet<Path> getTmpfsPaths() {
- ImmutableSet.Builder<Path> tmpfsPaths = ImmutableSet.builder();
- for (String tmpfsPath : sandboxOptions.sandboxTmpfsPath) {
- tmpfsPaths.add(blazeDirs.getFileSystem().getPath(tmpfsPath));
- }
- return tmpfsPaths.build();
- }
-
- private SortedMap<Path, Path> getReadOnlyBindMounts(
- BlazeDirectories blazeDirs, Path sandboxExecRoot) throws UserExecException {
- Path tmpPath = blazeDirs.getFileSystem().getPath("/tmp");
- final SortedMap<Path, Path> bindMounts = Maps.newTreeMap();
- if (blazeDirs.getWorkspace().startsWith(tmpPath)) {
- bindMounts.put(blazeDirs.getWorkspace(), blazeDirs.getWorkspace());
- }
- if (blazeDirs.getOutputBase().startsWith(tmpPath)) {
- bindMounts.put(blazeDirs.getOutputBase(), blazeDirs.getOutputBase());
- }
- for (ImmutableMap.Entry<String, String> additionalMountPath :
- sandboxOptions.sandboxAdditionalMounts) {
- try {
- final Path mountTarget = blazeDirs.getFileSystem().getPath(additionalMountPath.getValue());
- // If source path is relative, treat it as a relative path inside the execution root
- final Path mountSource = sandboxExecRoot.getRelative(additionalMountPath.getKey());
- // If a target has more than one source path, the latter one will take effect.
- bindMounts.put(mountTarget, mountSource);
- } catch (IllegalArgumentException e) {
- throw new UserExecException(
- String.format("Error occurred when analyzing bind mount pairs. %s", e.getMessage()));
- }
- }
- for (Path inaccessiblePath : getInaccessiblePaths()) {
- if (inaccessiblePath.isDirectory(Symlinks.NOFOLLOW)) {
- bindMounts.put(inaccessiblePath, inaccessibleHelperDir);
- } else {
- bindMounts.put(inaccessiblePath, inaccessibleHelperFile);
- }
- }
- validateBindMounts(bindMounts);
- return bindMounts;
- }
-
- /**
- * This method does the following things: - If mount source does not exist on the host system,
- * throw an error message - If mount target exists, check whether the source and target are of the
- * same type - If mount target does not exist on the host system, throw an error message
- *
- * @param bindMounts the bind mounts map with target as key and source as value
- * @throws UserExecException
- */
- private void validateBindMounts(SortedMap<Path, Path> bindMounts) throws UserExecException {
- for (SortedMap.Entry<Path, Path> bindMount : bindMounts.entrySet()) {
- final Path source = bindMount.getValue();
- final Path target = bindMount.getKey();
- // Mount source should exist in the file system
- if (!source.exists()) {
- throw new UserExecException(String.format("Mount source '%s' does not exist.", source));
- }
- // If target exists, but is not of the same type as the source, then we cannot mount it.
- if (target.exists()) {
- boolean areBothDirectories = source.isDirectory() && target.isDirectory();
- boolean isSourceFile = source.isFile() || source.isSymbolicLink();
- boolean isTargetFile = target.isFile() || target.isSymbolicLink();
- boolean areBothFiles = isSourceFile && isTargetFile;
- if (!(areBothDirectories || areBothFiles)) {
- // Source and target are not of the same type; we cannot mount it.
- throw new UserExecException(
- String.format(
- "Mount target '%s' is not of the same type as mount source '%s'.",
- target, source));
- }
- } else {
- // Mount target should exist in the file system
- throw new UserExecException(
- String.format(
- "Mount target '%s' does not exist. Bazel only supports bind mounting on top of "
- + "existing files/directories. Please create an empty file or directory at "
- + "the mount target path according to the type of mount source.",
- target));
- }
- }
- }
}
diff --git a/src/main/java/com/google/devtools/build/lib/sandbox/ProcessWrapperSandboxedSpawnRunner.java b/src/main/java/com/google/devtools/build/lib/sandbox/ProcessWrapperSandboxedSpawnRunner.java
new file mode 100644
index 0000000000..6394b4e7b7
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/sandbox/ProcessWrapperSandboxedSpawnRunner.java
@@ -0,0 +1,88 @@
+// Copyright 2017 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.Spawn;
+import com.google.devtools.build.lib.buildtool.BuildRequest;
+import com.google.devtools.build.lib.exec.SpawnResult;
+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.Path;
+import java.io.IOException;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.TimeUnit;
+
+/** Strategy that uses sandboxing to execute a process. */
+final class ProcessWrapperSandboxedSpawnRunner extends AbstractSandboxSpawnRunner {
+
+ public static boolean isSupported(CommandEnvironment cmdEnv) {
+ return OS.isPosixCompatible() && ProcessWrapperRunner.isSupported(cmdEnv);
+ }
+
+ private final Path execRoot;
+ private final String productName;
+ private final Path processWrapper;
+ private final LocalEnvProvider localEnvProvider;
+
+ ProcessWrapperSandboxedSpawnRunner(
+ CommandEnvironment cmdEnv,
+ BuildRequest buildRequest,
+ Path sandboxBase,
+ String productName) {
+ super(
+ cmdEnv,
+ sandboxBase,
+ buildRequest.getOptions(SandboxOptions.class));
+ this.execRoot = cmdEnv.getExecRoot();
+ this.productName = productName;
+ this.processWrapper = ProcessWrapperRunner.getProcessWrapper(cmdEnv);
+ this.localEnvProvider = OS.getCurrent() == OS.DARWIN
+ ? new XCodeLocalEnvProvider()
+ : LocalEnvProvider.UNMODIFIED;
+ }
+
+ @Override
+ 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());
+
+ 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);
+
+ 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);
+ }
+
+ @Override
+ protected String getName() {
+ return "processwrapper-sandbox";
+ }
+}
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 6fed1505ae..8c770a8bb2 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,39 +14,19 @@
package com.google.devtools.build.lib.sandbox;
-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.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.Path;
-import java.io.IOException;
-import java.util.List;
-import java.util.Map;
-import java.util.concurrent.TimeUnit;
/** Strategy that uses sandboxing to execute a process. */
+//TODO(ulfjack): This class only exists for this annotation. Find a better way to handle this!
@ExecutionStrategy(
name = {"sandboxed", "processwrapper-sandbox"},
contextType = SpawnActionContext.class
)
-public class ProcessWrapperSandboxedStrategy extends SandboxStrategy {
-
- public static boolean isSupported(CommandEnvironment cmdEnv) {
- return OS.isPosixCompatible() && ProcessWrapperRunner.isSupported(cmdEnv);
- }
-
- private final Path execRoot;
- private final String productName;
- private final Path processWrapper;
- private final LocalEnvProvider localEnvProvider;
-
+final class ProcessWrapperSandboxedStrategy extends SandboxStrategy {
ProcessWrapperSandboxedStrategy(
CommandEnvironment cmdEnv,
BuildRequest buildRequest,
@@ -54,44 +34,8 @@ public class ProcessWrapperSandboxedStrategy extends SandboxStrategy {
boolean verboseFailures,
String productName) {
super(
- cmdEnv,
- sandboxBase,
verboseFailures,
- buildRequest.getOptions(SandboxOptions.class));
- this.execRoot = cmdEnv.getExecRoot();
- this.productName = productName;
- this.processWrapper = ProcessWrapperRunner.getProcessWrapper(cmdEnv);
- this.localEnvProvider = OS.getCurrent() == OS.DARWIN
- ? new XCodeLocalEnvProvider()
- : LocalEnvProvider.UNMODIFIED;
- }
-
- @Override
- 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());
-
- 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);
-
- 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);
- }
-
- @Override
- protected String getName() {
- return "processwrapper-sandbox";
+ buildRequest.getOptions(SandboxOptions.class),
+ new ProcessWrapperSandboxedSpawnRunner(cmdEnv, buildRequest, sandboxBase, productName));
}
}
diff --git a/src/main/java/com/google/devtools/build/lib/sandbox/SandboxActionContextConsumer.java b/src/main/java/com/google/devtools/build/lib/sandbox/SandboxActionContextConsumer.java
index 653c9dafa7..64aaf908ad 100644
--- a/src/main/java/com/google/devtools/build/lib/sandbox/SandboxActionContextConsumer.java
+++ b/src/main/java/com/google/devtools/build/lib/sandbox/SandboxActionContextConsumer.java
@@ -36,9 +36,9 @@ final class SandboxActionContextConsumer implements ActionContextConsumer {
ImmutableMultimap.builder();
ImmutableMap.Builder<String, String> spawnContexts = ImmutableMap.builder();
- if (LinuxSandboxedStrategy.isSupported(cmdEnv)
- || DarwinSandboxedStrategy.isSupported(cmdEnv)
- || ProcessWrapperSandboxedStrategy.isSupported(cmdEnv)) {
+ if (LinuxSandboxedSpawnRunner.isSupported(cmdEnv)
+ || DarwinSandboxedSpawnRunner.isSupported(cmdEnv)
+ || ProcessWrapperSandboxedSpawnRunner.isSupported(cmdEnv)) {
// This makes the "sandboxed" strategy available via --spawn_strategy=sandboxed,
// but it is not necessarily the default.
contexts.put(SpawnActionContext.class, "sandboxed");
diff --git a/src/main/java/com/google/devtools/build/lib/sandbox/SandboxActionContextProvider.java b/src/main/java/com/google/devtools/build/lib/sandbox/SandboxActionContextProvider.java
index d9829a2cb7..c2f2055002 100644
--- a/src/main/java/com/google/devtools/build/lib/sandbox/SandboxActionContextProvider.java
+++ b/src/main/java/com/google/devtools/build/lib/sandbox/SandboxActionContextProvider.java
@@ -42,22 +42,22 @@ final class SandboxActionContextProvider extends ActionContextProvider {
// 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 (ProcessWrapperSandboxedStrategy.isSupported(cmdEnv)) {
+ if (ProcessWrapperSandboxedSpawnRunner.isSupported(cmdEnv)) {
contexts.add(
new ProcessWrapperSandboxedStrategy(
cmdEnv, buildRequest, sandboxBase, verboseFailures, productName));
}
// This is the preferred sandboxing strategy on Linux.
- if (LinuxSandboxedStrategy.isSupported(cmdEnv)) {
+ if (LinuxSandboxedSpawnRunner.isSupported(cmdEnv)) {
contexts.add(
LinuxSandboxedStrategy.create(cmdEnv, buildRequest, sandboxBase, verboseFailures));
}
// This is the preferred sandboxing strategy on macOS.
- if (DarwinSandboxedStrategy.isSupported(cmdEnv)) {
+ if (DarwinSandboxedSpawnRunner.isSupported(cmdEnv)) {
contexts.add(
- DarwinSandboxedStrategy.create(
+ new DarwinSandboxedStrategy(
cmdEnv, buildRequest, sandboxBase, verboseFailures, productName));
}
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 513b8d4df8..f290b812c1 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,24 +14,17 @@
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;
import com.google.devtools.build.lib.actions.SandboxedSpawnActionContext;
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.exec.SpawnExecException;
import com.google.devtools.build.lib.exec.SpawnInputExpander;
import com.google.devtools.build.lib.exec.SpawnResult;
@@ -39,48 +32,29 @@ 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.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 Path sandboxBase;
private final boolean verboseFailures;
private final SandboxOptions sandboxOptions;
- private final ImmutableSet<Path> inaccessiblePaths;
private final SpawnInputExpander spawnInputExpander;
+ private final AbstractSandboxSpawnRunner spawnRunner;
public SandboxStrategy(
- CommandEnvironment cmdEnv,
- Path sandboxBase,
boolean verboseFailures,
- SandboxOptions sandboxOptions) {
- this.sandboxBase = sandboxBase;
+ SandboxOptions sandboxOptions,
+ AbstractSandboxSpawnRunner spawnRunner) {
this.verboseFailures = verboseFailures;
this.sandboxOptions = sandboxOptions;
- this.inaccessiblePaths =
- sandboxOptions.getInaccessiblePaths(cmdEnv.getDirectories().getFileSystem());
this.spawnInputExpander = new SpawnInputExpander(false);
+ this.spawnRunner = spawnRunner;
}
@Override
@@ -101,27 +75,11 @@ abstract class SandboxStrategy implements SandboxedSpawnActionContext {
return;
}
- EventBus eventBus = actionExecutionContext.getEventBus();
- ActionExecutionMetadata owner = spawn.getResourceOwner();
- 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);
+ if (actionExecutionContext.reportsSubcommands()) {
+ actionExecutionContext.reportSubcommand(spawn);
}
- }
-
- private void actuallyExec(
- Spawn spawn,
- ActionExecutionContext actionExecutionContext,
- AtomicReference<Class<? extends SpawnActionContext>> writeOutputFiles)
- throws ExecException, IOException, InterruptedException {
final int timeoutSeconds = Spawns.getTimeoutSeconds(spawn);
+ final EventBus eventBus = actionExecutionContext.getEventBus();
SpawnExecutionPolicy policy = new SpawnExecutionPolicy() {
@Override
public ActionInputFileCache getActionInputFileCache() {
@@ -164,161 +122,31 @@ abstract class SandboxStrategy implements SandboxedSpawnActionContext {
@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 SpawnResult runSpawn(
- Spawn originalSpawn,
- SandboxedSpawn sandbox,
- SpawnExecutionPolicy policy,
- Path execRoot,
- int timeoutSeconds)
- throws ExecException, IOException, InterruptedException {
- try {
- 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 (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;
+ // TODO(ulfjack): We should report more details to the UI.
+ switch (state) {
+ case EXECUTING:
+ String strategyName = spawnRunner.getName();
+ eventBus.post(
+ ActionStatusMessage.runningStrategy(spawn.getResourceOwner(), strategyName));
+ break;
+ case SCHEDULING:
+ eventBus.post(ActionStatusMessage.schedulingStrategy(spawn.getResourceOwner()));
+ break;
+ default:
+ break;
}
- throw new SpawnExecException(
- message, result, /*forciblyRunRemotely=*/false, /*catastrophe=*/false);
- }
- return result;
- } finally {
- if (!sandboxOptions.sandboxDebug) {
- sandbox.delete();
}
+ };
+ SpawnResult result = spawnRunner.exec(spawn, policy);
+ if (result.status() != Status.SUCCESS || result.exitCode() != 0) {
+ String message =
+ CommandFailureUtils.describeCommandFailure(
+ true, spawn.getArguments(), spawn.getEnvironment(), null);
+ throw new SpawnExecException(
+ message, result, /*forciblyRunRemotely=*/false, /*catastrophe=*/false);
}
}
- 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 {
- 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();
- }
-
- 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;
- }
-
- /**
- * Returns a temporary directory that should be used as the sandbox directory for a single action.
- */
- protected Path getSandboxRoot() throws IOException {
- return sandboxBase.getRelative(
- java.nio.file.Files.createTempDirectory(
- java.nio.file.Paths.get(sandboxBase.getPathString()), "")
- .getFileName()
- .toString());
- }
-
- /**
- * Gets the list of directories that the spawn will assume to be writable.
- *
- * @throws IOException because we might resolve symlinks, which throws {@link IOException}.
- */
- protected ImmutableSet<Path> getWritableDirs(Path sandboxExecRoot, Map<String, String> env)
- throws IOException {
- // We have to make the TEST_TMPDIR directory writable if it is specified.
- ImmutableSet.Builder<Path> writablePaths = ImmutableSet.builder();
- writablePaths.add(sandboxExecRoot);
- 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) {
- Path path = fileSystem.getPath(writablePath);
- writablePaths.add(path.resolveSymbolicLinks());
- }
-
- return writablePaths.build();
- }
-
- protected ImmutableSet<Path> getInaccessiblePaths() {
- return inaccessiblePaths;
- }
-
- protected abstract String getName();
-
@Override
public String toString() {
return "sandboxed";