aboutsummaryrefslogtreecommitdiffhomepage
path: root/src/main/java/com/google/devtools/build/lib/sandbox/DarwinSandboxedStrategy.java
diff options
context:
space:
mode:
authorGravatar Philipp Wollermann <philwo@google.com>2016-08-31 12:07:40 +0000
committerGravatar Klaus Aehlig <aehlig@google.com>2016-08-31 14:51:58 +0000
commit5a50b4f1cd3eaaf52893a54debeae90ed1c65c0f (patch)
treee76582267ad3ac110159df743e331d5ab6fbc43a /src/main/java/com/google/devtools/build/lib/sandbox/DarwinSandboxedStrategy.java
parent5b261d5ae723165b0d716b865612afcff1d48cd1 (diff)
Refactor our sandboxing code.
-- MOS_MIGRATED_REVID=131817068
Diffstat (limited to 'src/main/java/com/google/devtools/build/lib/sandbox/DarwinSandboxedStrategy.java')
-rw-r--r--src/main/java/com/google/devtools/build/lib/sandbox/DarwinSandboxedStrategy.java425
1 files changed, 79 insertions, 346 deletions
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 df20524d0e..6188856d99 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
@@ -11,6 +11,7 @@
// 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;
@@ -19,12 +20,7 @@ import com.google.common.base.Predicates;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
-import com.google.common.io.Files;
import com.google.devtools.build.lib.actions.ActionExecutionContext;
-import com.google.devtools.build.lib.actions.ActionInput;
-import com.google.devtools.build.lib.actions.ActionInputHelper;
-import com.google.devtools.build.lib.actions.ActionStatusMessage;
-import com.google.devtools.build.lib.actions.Artifact;
import com.google.devtools.build.lib.actions.EnvironmentalExecException;
import com.google.devtools.build.lib.actions.ExecException;
import com.google.devtools.build.lib.actions.ExecutionStrategy;
@@ -32,20 +28,15 @@ import com.google.devtools.build.lib.actions.Executor;
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.AnalysisUtils;
import com.google.devtools.build.lib.analysis.BlazeDirectories;
import com.google.devtools.build.lib.analysis.config.RunUnder;
import com.google.devtools.build.lib.buildtool.BuildRequest;
-import com.google.devtools.build.lib.cmdline.Label;
-import com.google.devtools.build.lib.rules.cpp.CppCompileAction;
-import com.google.devtools.build.lib.rules.fileset.FilesetActionContext;
import com.google.devtools.build.lib.rules.test.TestRunnerAction;
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.standalone.StandaloneSpawnStrategy;
import com.google.devtools.build.lib.util.Preconditions;
-import com.google.devtools.build.lib.util.io.FileOutErr;
import com.google.devtools.build.lib.vfs.FileStatus;
import com.google.devtools.build.lib.vfs.FileSystem;
import com.google.devtools.build.lib.vfs.FileSystemUtils;
@@ -53,14 +44,11 @@ import com.google.devtools.build.lib.vfs.Path;
import com.google.devtools.build.lib.vfs.PathFragment;
import com.google.devtools.build.lib.vfs.SearchPath;
import com.google.devtools.build.lib.vfs.Symlinks;
-import java.io.File;
import java.io.IOException;
-import java.io.PrintWriter;
-import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
-import java.util.Map.Entry;
+import java.util.Set;
import java.util.UUID;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.atomic.AtomicInteger;
@@ -70,14 +58,14 @@ import java.util.concurrent.atomic.AtomicInteger;
name = {"sandboxed"},
contextType = SpawnActionContext.class
)
-public class DarwinSandboxedStrategy implements SpawnActionContext {
+public class DarwinSandboxedStrategy extends SandboxStrategy {
- private final ExecutorService backgroundWorkers;
+ private final BuildRequest buildRequest;
private final ImmutableMap<String, String> clientEnv;
private final BlazeDirectories blazeDirs;
private final Path execRoot;
- private final BuildRequest buildRequest;
- private final SandboxOptions sandboxOptions;
+ private final ExecutorService backgroundWorkers;
+ private final boolean sandboxDebug;
private final boolean verboseFailures;
private final String productName;
private final ImmutableList<Path> confPaths;
@@ -93,12 +81,13 @@ public class DarwinSandboxedStrategy implements SpawnActionContext {
boolean verboseFailures,
String productName,
ImmutableList<Path> confPaths) {
+ super(blazeDirs, verboseFailures, buildRequest.getOptions(SandboxOptions.class));
this.buildRequest = buildRequest;
- this.sandboxOptions = buildRequest.getOptions(SandboxOptions.class);
this.clientEnv = ImmutableMap.copyOf(clientEnv);
this.blazeDirs = blazeDirs;
this.execRoot = blazeDirs.getExecRoot();
this.backgroundWorkers = Preconditions.checkNotNull(backgroundWorkers);
+ this.sandboxDebug = buildRequest.getOptions(SandboxOptions.class).sandboxDebug;
this.verboseFailures = verboseFailures;
this.productName = productName;
this.confPaths = confPaths;
@@ -150,221 +139,114 @@ public class DarwinSandboxedStrategy implements SpawnActionContext {
return new String(res.getStdout(), UTF_8).trim();
}
- private int getTimeout(Spawn spawn) throws ExecException {
- String timeoutStr = spawn.getExecutionInfo().get("timeout");
- if (timeoutStr != null) {
- try {
- return Integer.parseInt(timeoutStr);
- } catch (NumberFormatException e) {
- throw new UserExecException("Could not parse timeout", e);
- }
- }
- return -1;
- }
-
@Override
public void exec(Spawn spawn, ActionExecutionContext actionExecutionContext)
throws ExecException {
Executor executor = actionExecutionContext.getExecutor();
// Certain actions can't run remotely or in a sandbox - pass them on to the standalone strategy.
- StandaloneSpawnStrategy standaloneStrategy =
- Preconditions.checkNotNull(executor.getContext(StandaloneSpawnStrategy.class));
if (!spawn.isRemotable()) {
- standaloneStrategy.exec(spawn, actionExecutionContext);
+ SandboxHelpers.fallbackToNonSandboxedExecution(spawn, actionExecutionContext, executor);
return;
}
- if (executor.reportsSubcommands()) {
- executor.reportSubcommand(
- Label.print(spawn.getOwner().getLabel())
- + " ["
- + spawn.getResourceOwner().prettyPrint()
- + "]",
- spawn.asShellCommand(executor.getExecRoot()));
- }
-
- executor
- .getEventBus()
- .post(ActionStatusMessage.runningStrategy(spawn.getResourceOwner(), "sandbox"));
-
- FileOutErr outErr = actionExecutionContext.getFileOutErr();
-
- // The execId is a unique ID just for this invocation of "exec".
- String execId = uuid + "-" + execCounter.getAndIncrement();
+ SandboxHelpers.reportSubcommand(executor, spawn);
+ SandboxHelpers.postActionStatusMessage(executor, spawn);
// Each invocation of "exec" gets its own sandbox.
- Path sandboxPath =
- blazeDirs.getOutputBase().getRelative(productName + "-sandbox").getRelative(execId);
-
- ImmutableSet<PathFragment> createDirs =
- createImportantDirs(
- standaloneStrategy.locallyDeterminedEnv(spawn.getEnvironment()), sandboxPath);
+ Path sandboxPath = SandboxHelpers.getSandboxRoot(blazeDirs, productName, uuid, execCounter);
+ Path sandboxExecRoot = sandboxPath.getRelative("execroot");
- int timeout = getTimeout(spawn);
+ ImmutableMap<String, String> spawnEnvironment =
+ StandaloneSpawnStrategy.locallyDeterminedEnv(execRoot, productName, spawn.getEnvironment());
- ImmutableSet.Builder<PathFragment> outputFiles = ImmutableSet.<PathFragment>builder();
- final DarwinSandboxRunner runner =
- getRunnerForExec(spawn, actionExecutionContext, sandboxPath, createDirs, outputFiles);
+ Set<Path> writableDirs = getWritableDirs(sandboxExecRoot, spawn.getEnvironment());
try {
- runner.run(
- spawn.getArguments(),
- standaloneStrategy.locallyDeterminedEnv(spawn.getEnvironment()),
- outErr,
- outputFiles.build(),
- timeout);
- } catch (IOException e) {
- throw new UserExecException("I/O error during sandboxed execution", e);
- } finally {
- // By deleting the sandbox directory in the background, we avoid having to wait for it to
- // complete before returning from the action, which improves performance.
- backgroundWorkers.execute(
- new Runnable() {
- @Override
- public void run() {
- try {
- while (!Thread.currentThread().isInterrupted()) {
- try {
- runner.cleanup();
- return;
- } catch (IOException e2) {
- // Sleep & retry.
- Thread.sleep(250);
- }
- }
- } catch (InterruptedException e) {
- // Exit.
- }
- }
- });
- }
- }
+ HardlinkedExecRoot hardlinkedExecRoot =
+ new HardlinkedExecRoot(execRoot, sandboxPath, sandboxExecRoot);
+ ImmutableSet<PathFragment> outputs = SandboxHelpers.getOutputFiles(spawn);
+ hardlinkedExecRoot.createFileSystem(
+ getMounts(spawn, actionExecutionContext), outputs, writableDirs);
- private DarwinSandboxRunner getRunnerForExec(
- Spawn spawn,
- ActionExecutionContext actionExecutionContext,
- Path sandboxPath,
- ImmutableSet<PathFragment> createDirs,
- ImmutableSet.Builder<PathFragment> outputFiles)
- throws ExecException {
- ImmutableMap<PathFragment, Path> linkPaths;
- try {
- // Gather all necessary linkPaths for the sandbox.
- linkPaths = getMounts(spawn, actionExecutionContext);
- } catch (IllegalArgumentException | IOException e) {
- throw new EnvironmentalExecException("Could not prepare mounts for sandbox execution", e);
- }
-
- for (PathFragment optionalOutput : spawn.getOptionalOutputFiles()) {
- Preconditions.checkArgument(!optionalOutput.isAbsolute());
- outputFiles.add(optionalOutput);
- }
- for (ActionInput output : spawn.getOutputFiles()) {
- outputFiles.add(new PathFragment(output.getExecPathString()));
- }
-
- DarwinSandboxRunner runner;
- try {
- Path sandboxConfigPath =
- generateScriptFile(sandboxPath, SandboxHelpers.shouldAllowNetwork(buildRequest, spawn));
+ DarwinSandboxRunner runner;
runner =
new DarwinSandboxRunner(
- execRoot,
sandboxPath,
- sandboxPath.getRelative("execroot"),
- sandboxConfigPath,
- linkPaths,
- createDirs,
- verboseFailures,
- sandboxOptions.sandboxDebug);
- } catch (IOException e) {
- throw new UserExecException("I/O error during sandboxed execution", e);
- }
-
- return runner;
- }
+ sandboxExecRoot,
+ getWritableDirs(sandboxExecRoot, spawnEnvironment),
+ getInaccessiblePaths(),
+ verboseFailures);
- private ImmutableSet<PathFragment> createImportantDirs(
- Map<String, String> env, Path sandboxPath) {
- ImmutableSet.Builder<PathFragment> dirs = ImmutableSet.builder();
- if (env.containsKey("TEST_TMPDIR")) {
- PathFragment testTmpDir = new PathFragment(env.get("TEST_TMPDIR"));
- if (!testTmpDir.isAbsolute()) {
- testTmpDir = sandboxPath.asFragment().getRelative("execroot").getRelative(testTmpDir);
+ try {
+ runner.run(
+ spawn.getArguments(),
+ spawnEnvironment,
+ actionExecutionContext.getFileOutErr(),
+ SandboxHelpers.getTimeout(spawn),
+ SandboxHelpers.shouldAllowNetwork(buildRequest, spawn));
+ } finally {
+ hardlinkedExecRoot.copyOutputs(execRoot, outputs);
+ if (!sandboxDebug) {
+ SandboxHelpers.lazyCleanup(backgroundWorkers, runner);
+ }
}
- dirs.add(testTmpDir);
+ } catch (IOException e) {
+ throw new UserExecException("I/O error during sandboxed execution", e);
}
- return dirs.build();
}
- private Path generateScriptFile(Path sandboxPath, boolean allowNetwork) throws IOException {
- FileSystemUtils.createDirectoryAndParents(sandboxPath);
- Path sandboxConfigPath =
- sandboxPath.getParentDirectory().getRelative(sandboxPath.getBaseName() + ".sb");
- try (PrintWriter out = new PrintWriter(sandboxConfigPath.getOutputStream())) {
- out.println("(version 1)");
- out.println("(debug deny)");
- out.println("(allow default)");
-
- // check network
- if (!allowNetwork) {
- out.println("(deny network*)");
- }
- out.println("(allow network* (local ip \"localhost:*\"))");
- out.println("(allow network* (remote ip \"localhost:*\"))");
- out.println("(allow network* (remote unix-socket (subpath \"/\")))");
- out.println("(allow network* (local unix-socket (subpath \"/\")))");
-
- // Non-readable path: workspace && exec_root
- out.println("(deny file-read* (subpath \"" + blazeDirs.getWorkspace() + "\"))");
- out.println("(deny file-read* (subpath \"" + execRoot + "\"))");
-
- // Almost everything is non-writable
- out.println("(deny file-write* (subpath \"/\"))");
+ @Override
+ protected ImmutableSet<Path> getWritableDirs(Path sandboxExecRoot, Map<String, String> env) {
+ FileSystem fs = sandboxExecRoot.getFileSystem();
+ ImmutableSet.Builder<Path> writableDirs = ImmutableSet.builder();
- allowWriteSubpath(out, blazeDirs.getFileSystem().getPath("/dev"));
+ writableDirs.addAll(super.getWritableDirs(sandboxExecRoot, env));
+ writableDirs.add(fs.getPath("/dev"));
- // Write access to sandbox
- allowWriteSubpath(out, sandboxPath);
+ String sysTmpDir = System.getenv("TMPDIR");
+ if (sysTmpDir != null) {
+ writableDirs.add(fs.getPath(sysTmpDir));
+ }
- // Write access to the system TMPDIR
- Path sysTmpDir = blazeDirs.getFileSystem().getPath(System.getenv("TMPDIR"));
- allowWriteSubpath(out, sysTmpDir);
- allowWriteSubpath(out, blazeDirs.getFileSystem().getPath("/tmp"));
+ writableDirs.add(fs.getPath("/tmp"));
- // Other tmpdir from getconf
- for (Path path : confPaths) {
- if (path.exists()) {
- allowWriteSubpath(out, path);
- }
+ // Other temporary directories from getconf.
+ for (Path path : confPaths) {
+ if (path.exists()) {
+ writableDirs.add(path);
}
}
- return sandboxConfigPath;
+ return writableDirs.build();
}
- private void allowWriteSubpath(PrintWriter out, Path path) throws IOException {
- out.println("(allow file-write* (subpath \"" + path.getPathString() + "\"))");
- Path resolvedPath = path.resolveSymbolicLinks();
- if (!resolvedPath.equals(path)) {
- out.println("(allow file-write* (subpath \"" + resolvedPath.getPathString() + "\"))");
- }
+ @Override
+ protected ImmutableSet<Path> getInaccessiblePaths() {
+ ImmutableSet.Builder<Path> inaccessiblePaths = ImmutableSet.builder();
+ inaccessiblePaths.addAll(super.getInaccessiblePaths());
+ inaccessiblePaths.add(blazeDirs.getWorkspace());
+ inaccessiblePaths.add(execRoot);
+ return inaccessiblePaths.build();
}
- private ImmutableMap<PathFragment, Path> getMounts(
- Spawn spawn, ActionExecutionContext executionContext) throws IOException, ExecException {
- ImmutableMap.Builder<PathFragment, Path> result = new ImmutableMap.Builder<>();
- result.putAll(mountInputs(spawn, executionContext));
+ private Map<PathFragment, Path> getMounts(Spawn spawn, ActionExecutionContext executionContext)
+ throws ExecException {
+ try {
+ Map<PathFragment, Path> mounts = new HashMap<>();
+ mountInputs(mounts, spawn, executionContext);
- Map<PathFragment, Path> unfinalized = new HashMap<>();
- unfinalized.putAll(mountRunfilesFromManifests(spawn));
- unfinalized.putAll(mountRunfilesFromSuppliers(spawn));
- unfinalized.putAll(mountFilesFromFilesetManifests(spawn, executionContext));
- unfinalized.putAll(mountRunUnderCommand(spawn));
- result.putAll(finalizeLinks(unfinalized));
+ Map<PathFragment, Path> unfinalized = new HashMap<>();
+ mountRunfilesFromManifests(unfinalized, spawn);
+ mountRunfilesFromSuppliers(unfinalized, spawn);
+ mountFilesFromFilesetManifests(unfinalized, spawn, executionContext);
+ mountRunUnderCommand(unfinalized, spawn);
+ mounts.putAll(finalizeLinks(unfinalized));
- return result.build();
+ return mounts;
+ } catch (IllegalArgumentException | IOException e) {
+ throw new EnvironmentalExecException("Could not prepare mounts for sandbox execution", e);
+ }
}
private ImmutableMap<PathFragment, Path> finalizeLinks(Map<PathFragment, Path> unfinalized)
@@ -400,87 +282,6 @@ public class DarwinSandboxedStrategy implements SpawnActionContext {
finalizedMounts.put(target, source);
}
- /** Mount all inputs of the spawn. */
- private Map<PathFragment, Path> mountInputs(
- Spawn spawn, ActionExecutionContext actionExecutionContext) {
- Map<PathFragment, Path> mounts = new HashMap<>();
-
- List<ActionInput> inputs =
- ActionInputHelper.expandArtifacts(
- spawn.getInputFiles(), actionExecutionContext.getArtifactExpander());
-
- if (spawn.getResourceOwner() instanceof CppCompileAction) {
- CppCompileAction action = (CppCompileAction) spawn.getResourceOwner();
- if (action.shouldScanIncludes()) {
- inputs.addAll(action.getAdditionalInputs());
- }
- }
-
- for (ActionInput input : inputs) {
- if (input.getExecPathString().contains("internal/_middlemen/")) {
- continue;
- }
- Path mount = execRoot.getRelative(input.getExecPathString());
- mounts.put(new PathFragment(input.getExecPathString()), mount);
- }
- return mounts;
- }
-
- /** Mount all runfiles that the spawn needs as specified in its runfiles manifests. */
- private Map<PathFragment, Path> mountRunfilesFromManifests(Spawn spawn)
- throws IOException, ExecException {
- Map<PathFragment, Path> mounts = new HashMap<>();
- for (Entry<PathFragment, Artifact> manifest : spawn.getRunfilesManifests().entrySet()) {
- String manifestFilePath = manifest.getValue().getPath().getPathString();
- Preconditions.checkState(!manifest.getKey().isAbsolute());
-
- mounts.putAll(parseManifestFile(manifest.getKey(), new File(manifestFilePath), false, ""));
- }
- return mounts;
- }
-
- /** Mount all files that the spawn needs as specified in its fileset manifests. */
- private Map<PathFragment, Path> mountFilesFromFilesetManifests(
- Spawn spawn, ActionExecutionContext executionContext) throws IOException, ExecException {
- final FilesetActionContext filesetContext =
- executionContext.getExecutor().getContext(FilesetActionContext.class);
- Map<PathFragment, Path> mounts = new HashMap<>();
- for (Artifact fileset : spawn.getFilesetManifests()) {
- Path manifest =
- execRoot.getRelative(AnalysisUtils.getManifestPathFromFilesetPath(fileset.getExecPath()));
-
- mounts.putAll(
- parseManifestFile(
- fileset.getExecPath(),
- manifest.getPathFile(),
- true,
- filesetContext.getWorkspaceName()));
- }
- return mounts;
- }
-
- /** Mount all runfiles that the spawn needs as specified via its runfiles suppliers. */
- private Map<PathFragment, Path> mountRunfilesFromSuppliers(Spawn spawn) throws IOException {
- Map<PathFragment, Path> mounts = new HashMap<>();
- FileSystem fs = blazeDirs.getFileSystem();
- Map<PathFragment, Map<PathFragment, Artifact>> rootsAndMappings =
- spawn.getRunfilesSupplier().getMappings();
- for (Entry<PathFragment, Map<PathFragment, Artifact>> rootAndMappings :
- rootsAndMappings.entrySet()) {
- PathFragment root =
- fs.getRootDirectory().getRelative(rootAndMappings.getKey()).relativeTo(execRoot);
- for (Entry<PathFragment, Artifact> mapping : rootAndMappings.getValue().entrySet()) {
- Artifact sourceArtifact = mapping.getValue();
- Path source = (sourceArtifact != null) ? sourceArtifact.getPath() : fs.getPath("/dev/null");
-
- Preconditions.checkArgument(!mapping.getKey().isAbsolute());
- PathFragment target = root.getRelative(mapping.getKey());
- mounts.put(target, source);
- }
- }
- return mounts;
- }
-
/**
* If a --run_under= option is set and refers to a command via its path (as opposed to via its
* label), we have to mount this. Note that this is best effort and works fine for shell scripts
@@ -489,9 +290,7 @@ public class DarwinSandboxedStrategy implements SpawnActionContext {
* <p>If --run_under= refers to a label, it is automatically provided in the spawn's input files,
* so mountInputs() will catch that case.
*/
- private Map<PathFragment, Path> mountRunUnderCommand(Spawn spawn) {
- Map<PathFragment, Path> mounts = new HashMap<>();
-
+ private void mountRunUnderCommand(Map<PathFragment, Path> mounts, Spawn spawn) {
if (spawn.getResourceOwner() instanceof TestRunnerAction) {
TestRunnerAction testRunnerAction = ((TestRunnerAction) spawn.getResourceOwner());
RunUnder runUnder = testRunnerAction.getExecutionSettings().getRunUnder();
@@ -514,71 +313,5 @@ public class DarwinSandboxedStrategy implements SpawnActionContext {
}
}
}
- return mounts;
- }
-
- private Map<PathFragment, Path> parseManifestFile(
- PathFragment targetDirectory,
- File manifestFile,
- boolean isFilesetManifest,
- String workspaceName)
- throws IOException, ExecException {
- Map<PathFragment, Path> mounts = new HashMap<>();
- int lineNum = 0;
- for (String line : Files.readLines(manifestFile, StandardCharsets.UTF_8)) {
- if (isFilesetManifest && (++lineNum % 2 == 0)) {
- continue;
- }
- if (line.isEmpty()) {
- continue;
- }
-
- String[] fields = line.trim().split(" ");
-
- PathFragment targetPath;
- if (isFilesetManifest) {
- PathFragment targetPathFragment = new PathFragment(fields[0]);
- if (!workspaceName.isEmpty()) {
- if (!targetPathFragment.getSegment(0).equals(workspaceName)) {
- throw new EnvironmentalExecException(
- "Fileset manifest line must start with workspace name");
- }
- targetPathFragment = targetPathFragment.subFragment(1, targetPathFragment.segmentCount());
- }
- targetPath = targetDirectory.getRelative(targetPathFragment);
- } else {
- targetPath = targetDirectory.getRelative(fields[0]);
- }
-
- Path source;
- switch (fields.length) {
- case 1:
- source = blazeDirs.getFileSystem().getPath("/dev/null");
- break;
- case 2:
- source = blazeDirs.getFileSystem().getPath(fields[1]);
- break;
- default:
- throw new IllegalStateException("'" + line + "' splits into more than 2 parts");
- }
-
- mounts.put(targetPath, source);
- }
- return mounts;
- }
-
- @Override
- public boolean willExecuteRemotely(boolean remotable) {
- return false;
- }
-
- @Override
- public String toString() {
- return "sandboxed";
- }
-
- @Override
- public boolean shouldPropagateExecException() {
- return verboseFailures && sandboxOptions.sandboxDebug;
}
}