Refactor our sandboxing code.
--
MOS_MIGRATED_REVID=131817068
diff --git a/src/main/java/com/google/devtools/build/lib/sandbox/DarwinSandboxRunner.java b/src/main/java/com/google/devtools/build/lib/sandbox/DarwinSandboxRunner.java
index b871d08..e3da6ab 100644
--- a/src/main/java/com/google/devtools/build/lib/sandbox/DarwinSandboxRunner.java
+++ b/src/main/java/com/google/devtools/build/lib/sandbox/DarwinSandboxRunner.java
@@ -15,70 +15,48 @@
package com.google.devtools.build.lib.sandbox;
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.UserExecException;
-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.TerminationStatus;
+import com.google.devtools.build.lib.shell.KillableObserver;
import com.google.devtools.build.lib.shell.TimeoutKillableObserver;
-import com.google.devtools.build.lib.util.CommandFailureUtils;
-import com.google.devtools.build.lib.util.io.FileOutErr;
-import com.google.devtools.build.lib.vfs.FileSystem;
-import com.google.devtools.build.lib.vfs.FileSystemUtils;
import com.google.devtools.build.lib.vfs.Path;
-import com.google.devtools.build.lib.vfs.PathFragment;
import java.io.File;
import java.io.IOException;
import java.io.PrintWriter;
-import java.nio.file.Files;
import java.util.ArrayList;
-import java.util.Collection;
import java.util.List;
+import java.util.Map;
+import java.util.Set;
/**
* Helper class for running the namespace sandbox. This runner prepares environment inside the
* sandbox, handles sandbox output, performs cleanup and changes invocation if necessary.
*/
-public class DarwinSandboxRunner {
+final class DarwinSandboxRunner extends SandboxRunner {
+ private static final String SANDBOX_EXEC = "/usr/bin/sandbox-exec";
- private final Path execRoot;
- private final Path sandboxPath;
private final Path sandboxExecRoot;
private final Path argumentsFilePath;
- private final ImmutableSet<PathFragment> createDirs;
- private final boolean verboseFailures;
- private final boolean sandboxDebug;
+ private final Set<Path> writableDirs;
+ private final Set<Path> inaccessiblePaths;
- private final Path sandboxConfigPath;
- private final ImmutableMap<PathFragment, Path> linkPaths;
-
- public DarwinSandboxRunner(
- Path execRoot,
+ DarwinSandboxRunner(
Path sandboxPath,
Path sandboxExecRoot,
- Path sandboxConfigPath,
- ImmutableMap<PathFragment, Path> linkPaths,
- ImmutableSet<PathFragment> createDirs,
- boolean verboseFailures,
- boolean sandboxDebug) {
- this.execRoot = execRoot;
- this.sandboxPath = sandboxPath;
+ Set<Path> writableDirs,
+ Set<Path> inaccessiblePaths,
+ boolean verboseFailures) {
+ super(sandboxPath, sandboxExecRoot, verboseFailures);
this.sandboxExecRoot = sandboxExecRoot;
- this.argumentsFilePath =
- sandboxPath.getParentDirectory().getRelative(sandboxPath.getBaseName() + ".params");
- this.createDirs = createDirs;
- this.verboseFailures = verboseFailures;
- this.sandboxDebug = sandboxDebug;
- this.sandboxConfigPath = sandboxConfigPath;
- this.linkPaths = linkPaths;
+ this.argumentsFilePath = sandboxPath.getRelative("sandbox.sb");
+ this.writableDirs = writableDirs;
+ this.inaccessiblePaths = inaccessiblePaths;
}
static boolean isSupported() {
List<String> args = new ArrayList<>();
- args.add("sandbox-exec");
+ args.add(SANDBOX_EXEC);
args.add("-p");
args.add("(version 1) (allow default)");
args.add("/usr/bin/true");
@@ -101,193 +79,66 @@
return true;
}
- /**
- * Runs given command inside the sandbox.
- *
- * @param spawnArguments - arguments of spawn to run inside the sandbox
- * @param env - environment to run sandbox in
- * @param outErr - error output to capture sandbox's and command's stderr
- * @param outputs - files to extract from the sandbox, paths are relative to the exec root
- * @throws ExecException
- */
- public void run(
- List<String> spawnArguments,
- ImmutableMap<String, String> env,
- FileOutErr outErr,
- Collection<PathFragment> outputs,
- int timeout)
- throws IOException, ExecException {
- createFileSystem(outputs);
-
- List<String> commandLineArgs = sandboxPreperationAndGetArgs(spawnArguments, outErr);
-
- Command cmd =
- new Command(commandLineArgs.toArray(new String[0]), env, sandboxExecRoot.getPathFile());
-
- try {
- cmd.execute(
- /* stdin */ new byte[] {},
- (timeout >= 0) ? new TimeoutKillableObserver(timeout * 1000) : Command.NO_OBSERVER,
- outErr.getOutputStream(),
- outErr.getErrorStream(),
- /* killSubprocessOnInterrupt */ true);
- } catch (CommandException e) {
- boolean timedOut = false;
- if (e instanceof AbnormalTerminationException) {
- TerminationStatus status =
- ((AbnormalTerminationException) e).getResult().getTerminationStatus();
- timedOut = !status.exited() && (status.getTerminatingSignal() == 15 /* SIGTERM */);
- }
- String message =
- CommandFailureUtils.describeCommandFailure(
- verboseFailures, commandLineArgs, env, sandboxExecRoot.getPathString());
- throw new UserExecException(message, e, timedOut);
- } finally {
- copyOutputs(outputs);
- }
- }
-
- private void createFileSystem(Collection<PathFragment> outputs) throws IOException {
- FileSystemUtils.createDirectoryAndParents(sandboxPath);
-
- // Prepare the output directories in the sandbox.
- for (PathFragment output : outputs) {
- FileSystemUtils.createDirectoryAndParents(
- sandboxExecRoot.getRelative(output.getParentDirectory()));
- }
- }
-
- private void copyOutputs(Collection<PathFragment> outputs) throws IOException {
- for (PathFragment output : outputs) {
- Path source = sandboxExecRoot.getRelative(output);
- Path target = execRoot.getRelative(output);
- FileSystemUtils.createDirectoryAndParents(target.getParentDirectory());
- if (source.isFile() || source.isSymbolicLink()) {
- com.google.common.io.Files.move(source.getPathFile(), target.getPathFile());
- }
- }
- }
-
- public void cleanup() throws IOException {
- if (sandboxPath.exists()) {
- FileSystemUtils.deleteTree(sandboxPath);
- }
- if (!sandboxDebug && argumentsFilePath.exists()) {
- argumentsFilePath.delete();
- }
- }
-
- private List<String> sandboxPreperationAndGetArgs(List<String> spawnArguments, FileOutErr outErr)
+ @Override
+ protected Command getCommand(
+ List<String> arguments, Map<String, String> environment, int timeout, boolean allowNetwork)
throws IOException {
- FileSystem fs = sandboxPath.getFileSystem();
- PrintWriter errWriter = new PrintWriter(outErr.getErrorStream());
+ writeConfig(allowNetwork);
+
List<String> commandLineArgs = new ArrayList<>();
-
- if (sandboxDebug) {
- errWriter.printf("sandbox root is %s\n", sandboxPath.toString());
- errWriter.printf("working dir is %s\n", sandboxExecRoot.toString());
- }
-
- // Create all needed directories.
- for (PathFragment createDir : createDirs) {
- Path dir;
- if (createDir.isAbsolute()) {
- dir = fs.getPath(createDir);
- } else {
- dir = sandboxPath.getRelative(createDir);
- }
- if (sandboxDebug) {
- errWriter.printf("createdir: %s\n", dir);
- }
- FileSystemUtils.createDirectoryAndParents(dir);
- }
-
- // Link all the inputs.
- linkInputs(linkPaths, errWriter);
-
- errWriter.flush();
-
- commandLineArgs.add("/usr/bin/sandbox-exec");
+ commandLineArgs.add(SANDBOX_EXEC);
commandLineArgs.add("-f");
- commandLineArgs.add(sandboxConfigPath.getPathString());
- commandLineArgs.addAll(spawnArguments);
-
- return commandLineArgs;
+ commandLineArgs.add(argumentsFilePath.getPathString());
+ commandLineArgs.addAll(arguments);
+ return new Command(
+ commandLineArgs.toArray(new String[0]), environment, sandboxExecRoot.getPathFile());
}
- /**
- * Make all specified inputs available in the sandbox.
- *
- * We want the sandboxed process to have access only to these input files and not anything else
- * from the workspace. Furthermore, the process should not be able to modify these input files.
- * We achieve this by hardlinking all input files into a temporary "inputs" directory, then
- * symlinking them into their correct place inside the sandbox.
- *
- * The hardlinks / symlinks combination (as opposed to simply directly hardlinking to the final
- * destination) is necessary, because we build a solib symlink tree for shared libraries where the
- * original file and the created symlink have two different file names (libblaze_util.so vs.
- * src_Stest_Scpp_Sblaze_Uutil_Utest.so) and our cc_wrapper.sh needs to be able to figure out both
- * names (by following solib symlinks back) to modify the paths to the shared libraries in
- * cc_binaries.
- */
- private void linkInputs(ImmutableMap<PathFragment, Path> inputs, PrintWriter errWriter)
- throws IOException {
- // create directory for input files
- Path inputsDir = sandboxPath.getRelative("inputs");
- if (!inputsDir.exists()) {
- inputsDir.createDirectory();
- }
+ private void writeConfig(boolean allowNetwork) throws IOException {
+ try (PrintWriter out = new PrintWriter(argumentsFilePath.getOutputStream())) {
+ // Note: In Apple's sandbox configuration language, the *last* matching rule wins.
+ out.println("(version 1)");
+ out.println("(debug deny)");
+ out.println("(allow default)");
- for (ImmutableMap.Entry<PathFragment, Path> entry : inputs.entrySet()) {
- // hardlink, resolve symlink here instead in finalizeLinks
- Path hardlinkOldPath = entry.getValue().resolveSymbolicLinks();
- Path hardlinkNewPath =
- hardlinkOldPath.startsWith(execRoot)
- ? inputsDir.getRelative(hardlinkOldPath.relativeTo(execRoot))
- : inputsDir.getRelative(entry.getKey());
- if (sandboxDebug) {
- errWriter.printf("hardlink: %s -> %s\n", hardlinkNewPath, hardlinkOldPath);
- }
- try {
- createHardLink(hardlinkNewPath, hardlinkOldPath);
- } catch (IOException e) {
- // Creating a hardlink might fail when the input file and the sandbox directory are not on
- // the same filesystem / device. Then we use symlink instead.
- hardlinkNewPath.createSymbolicLink(hardlinkOldPath);
+ if (!allowNetwork) {
+ out.println("(deny network*)");
}
- // symlink
- Path symlinkNewPath = sandboxExecRoot.getRelative(entry.getKey());
- if (sandboxDebug) {
- errWriter.printf("symlink: %s -> %s\n", hardlinkNewPath, symlinkNewPath);
+ 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 \"/\")))");
+
+ for (Path inaccessiblePath : inaccessiblePaths) {
+ out.println("(deny file-read* (subpath \"" + inaccessiblePath + "\"))");
}
- FileSystemUtils.createDirectoryAndParents(symlinkNewPath.getParentDirectory());
- symlinkNewPath.createSymbolicLink(hardlinkNewPath);
+
+ // Almost everything else is read-only.
+ out.println("(deny file-write* (subpath \"/\"))");
+
+ allowWriteSubpath(out, sandboxExecRoot);
+ for (Path path : writableDirs) {
+ allowWriteSubpath(out, path);
+ }
}
}
- // TODO(yueg): import unix.FilesystemUtils and use FilesystemUtils.createHardLink() instead
- private void createHardLink(Path target, Path source) throws IOException {
- java.nio.file.Path targetNio = java.nio.file.Paths.get(target.toString());
- java.nio.file.Path sourceNio = java.nio.file.Paths.get(source.toString());
+ 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() + "\"))");
+ }
+ }
- if (!source.exists() || target.exists()) {
- return;
- }
- // Regular file
- if (source.isFile()) {
- Path parentDir = target.getParentDirectory();
- if (!parentDir.exists()) {
- FileSystemUtils.createDirectoryAndParents(parentDir);
- }
- Files.createLink(targetNio, sourceNio);
- // Directory
- } else if (source.isDirectory()) {
- Collection<Path> subpaths = source.getDirectoryEntries();
- for (Path sourceSubpath : subpaths) {
- Path targetSubpath = target.getRelative(sourceSubpath.relativeTo(source));
- createHardLink(targetSubpath, sourceSubpath);
- }
- }
+ @Override
+ protected KillableObserver getCommandObserver(int timeout) {
+ return (timeout >= 0) ? new TimeoutKillableObserver(timeout * 1000) : Command.NO_OBSERVER;
+ }
+
+ @Override
+ protected int getSignalOnTimeout() {
+ return 15; /* SIGTERM */
}
}
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 df20524..6188856 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.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.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.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 @@
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 @@
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 @@
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);
+ Path sandboxPath = SandboxHelpers.getSandboxRoot(blazeDirs, productName, uuid, execCounter);
+ Path sandboxExecRoot = sandboxPath.getRelative("execroot");
- ImmutableSet<PathFragment> createDirs =
- createImportantDirs(
- standaloneStrategy.locallyDeterminedEnv(spawn.getEnvironment()), sandboxPath);
+ ImmutableMap<String, String> spawnEnvironment =
+ StandaloneSpawnStrategy.locallyDeterminedEnv(execRoot, productName, spawn.getEnvironment());
- int timeout = getTimeout(spawn);
-
- 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);
+ HardlinkedExecRoot hardlinkedExecRoot =
+ new HardlinkedExecRoot(execRoot, sandboxPath, sandboxExecRoot);
+ ImmutableSet<PathFragment> outputs = SandboxHelpers.getOutputFiles(spawn);
+ hardlinkedExecRoot.createFileSystem(
+ getMounts(spawn, actionExecutionContext), outputs, writableDirs);
+
+ DarwinSandboxRunner runner;
+ runner =
+ new DarwinSandboxRunner(
+ sandboxPath,
+ sandboxExecRoot,
+ getWritableDirs(sandboxExecRoot, spawnEnvironment),
+ getInaccessiblePaths(),
+ verboseFailures);
+
+ 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);
+ }
+ }
} 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.
- }
- }
- });
}
}
- private DarwinSandboxRunner getRunnerForExec(
- Spawn spawn,
- ActionExecutionContext actionExecutionContext,
- Path sandboxPath,
- ImmutableSet<PathFragment> createDirs,
- ImmutableSet.Builder<PathFragment> outputFiles)
+ @Override
+ protected ImmutableSet<Path> getWritableDirs(Path sandboxExecRoot, Map<String, String> env) {
+ FileSystem fs = sandboxExecRoot.getFileSystem();
+ ImmutableSet.Builder<Path> writableDirs = ImmutableSet.builder();
+
+ writableDirs.addAll(super.getWritableDirs(sandboxExecRoot, env));
+ writableDirs.add(fs.getPath("/dev"));
+
+ String sysTmpDir = System.getenv("TMPDIR");
+ if (sysTmpDir != null) {
+ writableDirs.add(fs.getPath(sysTmpDir));
+ }
+
+ writableDirs.add(fs.getPath("/tmp"));
+
+ // Other temporary directories from getconf.
+ for (Path path : confPaths) {
+ if (path.exists()) {
+ writableDirs.add(path);
+ }
+ }
+
+ return writableDirs.build();
+ }
+
+ @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 Map<PathFragment, Path> getMounts(Spawn spawn, ActionExecutionContext executionContext)
throws ExecException {
- ImmutableMap<PathFragment, Path> linkPaths;
try {
- // Gather all necessary linkPaths for the sandbox.
- linkPaths = getMounts(spawn, actionExecutionContext);
+ Map<PathFragment, Path> mounts = new HashMap<>();
+ mountInputs(mounts, spawn, executionContext);
+
+ Map<PathFragment, Path> unfinalized = new HashMap<>();
+ mountRunfilesFromManifests(unfinalized, spawn);
+ mountRunfilesFromSuppliers(unfinalized, spawn);
+ mountFilesFromFilesetManifests(unfinalized, spawn, executionContext);
+ mountRunUnderCommand(unfinalized, spawn);
+ mounts.putAll(finalizeLinks(unfinalized));
+
+ return mounts;
} 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));
- 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;
- }
-
- 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);
- }
- dirs.add(testTmpDir);
- }
- 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 \"/\"))");
-
- allowWriteSubpath(out, blazeDirs.getFileSystem().getPath("/dev"));
-
- // Write access to sandbox
- allowWriteSubpath(out, sandboxPath);
-
- // Write access to the system TMPDIR
- Path sysTmpDir = blazeDirs.getFileSystem().getPath(System.getenv("TMPDIR"));
- allowWriteSubpath(out, sysTmpDir);
- allowWriteSubpath(out, blazeDirs.getFileSystem().getPath("/tmp"));
-
- // Other tmpdir from getconf
- for (Path path : confPaths) {
- if (path.exists()) {
- allowWriteSubpath(out, path);
- }
- }
- }
-
- return sandboxConfigPath;
- }
-
- 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() + "\"))");
- }
- }
-
- 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));
-
- 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));
-
- return result.build();
}
private ImmutableMap<PathFragment, Path> finalizeLinks(Map<PathFragment, Path> unfinalized)
@@ -400,87 +282,6 @@
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 @@
* <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 @@
}
}
}
- 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;
}
}
diff --git a/src/main/java/com/google/devtools/build/lib/sandbox/HardlinkedExecRoot.java b/src/main/java/com/google/devtools/build/lib/sandbox/HardlinkedExecRoot.java
new file mode 100644
index 0000000..8aeabe3
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/sandbox/HardlinkedExecRoot.java
@@ -0,0 +1,149 @@
+// Copyright 2016 The Bazel Authors. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.devtools.build.lib.sandbox;
+
+import com.google.common.collect.ImmutableMap;
+import com.google.common.io.Files;
+import com.google.devtools.build.lib.vfs.FileSystemUtils;
+import com.google.devtools.build.lib.vfs.Path;
+import com.google.devtools.build.lib.vfs.PathFragment;
+import java.io.IOException;
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * Creates an execRoot for a Spawn that contains input files as symlinks to hardlinks of the
+ * original input files.
+ */
+public class HardlinkedExecRoot implements SandboxExecRoot {
+
+ private final Path execRoot;
+ private final Path sandboxPath;
+ private final Path sandboxExecRoot;
+
+ public HardlinkedExecRoot(Path execRoot, Path sandboxPath, Path sandboxExecRoot) {
+ this.execRoot = execRoot;
+ this.sandboxPath = sandboxPath;
+ this.sandboxExecRoot = sandboxExecRoot;
+ }
+
+ @Override
+ public void createFileSystem(
+ Map<PathFragment, Path> inputs, Collection<PathFragment> outputs, Set<Path> writableDirs)
+ throws IOException {
+ Set<Path> createdDirs = new HashSet<>();
+ FileSystemUtils.createDirectoryAndParentsWithCache(createdDirs, sandboxExecRoot);
+ createDirectoriesForOutputs(outputs, createdDirs);
+
+ // Create all needed directories.
+ for (Path createDir : writableDirs) {
+ FileSystemUtils.createDirectoryAndParents(createDir);
+ }
+
+ // Link all the inputs.
+ linkInputs(inputs);
+ }
+
+ private void createDirectoriesForOutputs(Collection<PathFragment> outputs, Set<Path> createdDirs)
+ throws IOException {
+ // Prepare the output directories in the sandbox.
+ for (PathFragment output : outputs) {
+ FileSystemUtils.createDirectoryAndParentsWithCache(
+ createdDirs, sandboxExecRoot.getRelative(output.getParentDirectory()));
+ }
+ }
+
+ /**
+ * Make all specified inputs available in the sandbox.
+ *
+ * <p>We want the sandboxed process to have access only to these input files and not anything else
+ * from the workspace. Furthermore, the process should not be able to modify these input files. We
+ * achieve this by hardlinking all input files into a temporary "inputs" directory, then
+ * symlinking them into their correct place inside the sandbox.
+ *
+ * <p>The hardlinks / symlinks combination (as opposed to simply directly hardlinking to the final
+ * destination) is necessary, because we build a solib symlink tree for shared libraries where the
+ * original file and the created symlink have two different file names (libblaze_util.so vs.
+ * src_Stest_Scpp_Sblaze_Uutil_Utest.so) and our cc_wrapper.sh needs to be able to figure out both
+ * names (by following solib symlinks back) to modify the paths to the shared libraries in
+ * cc_binaries.
+ */
+ private void linkInputs(Map<PathFragment, Path> inputs) throws IOException {
+ // Create directory for input files.
+ Path inputsDir = sandboxPath.getRelative("inputs");
+ if (!inputsDir.exists()) {
+ inputsDir.createDirectory();
+ }
+
+ for (ImmutableMap.Entry<PathFragment, Path> entry : inputs.entrySet()) {
+ // Hardlink, resolve symlink here instead in finalizeLinks.
+ Path source = entry.getValue().resolveSymbolicLinks();
+ Path target =
+ source.startsWith(execRoot)
+ ? inputsDir.getRelative(source.relativeTo(execRoot))
+ : inputsDir.getRelative(entry.getKey());
+ try {
+ createHardLink(target, source);
+ } catch (IOException e) {
+ // Creating a hardlink might fail when the input file and the sandbox directory are not on
+ // the same filesystem / device. Then we use symlink instead.
+ target.createSymbolicLink(source);
+ }
+
+ // symlink
+ Path symlinkNewPath = sandboxExecRoot.getRelative(entry.getKey());
+ FileSystemUtils.createDirectoryAndParents(symlinkNewPath.getParentDirectory());
+ symlinkNewPath.createSymbolicLink(target);
+ }
+ }
+
+ // TODO(yueg): import unix.FilesystemUtils and use FilesystemUtils.createHardLink() instead
+ private void createHardLink(Path target, Path source) throws IOException {
+ java.nio.file.Path targetNio = java.nio.file.Paths.get(target.toString());
+ java.nio.file.Path sourceNio = java.nio.file.Paths.get(source.toString());
+
+ if (!source.exists() || target.exists()) {
+ return;
+ }
+ // Regular file
+ if (source.isFile()) {
+ Path parentDir = target.getParentDirectory();
+ if (!parentDir.exists()) {
+ FileSystemUtils.createDirectoryAndParents(parentDir);
+ }
+ java.nio.file.Files.createLink(targetNio, sourceNio);
+ // Directory
+ } else if (source.isDirectory()) {
+ Collection<Path> subpaths = source.getDirectoryEntries();
+ for (Path sourceSubpath : subpaths) {
+ Path targetSubpath = target.getRelative(sourceSubpath.relativeTo(source));
+ createHardLink(targetSubpath, sourceSubpath);
+ }
+ }
+ }
+
+ @Override
+ public void copyOutputs(Path execRoot, Collection<PathFragment> outputs) throws IOException {
+ for (PathFragment output : outputs) {
+ Path source = sandboxExecRoot.getRelative(output);
+ if (source.isFile() || source.isSymbolicLink()) {
+ Path target = execRoot.getRelative(output);
+ Files.move(source.getPathFile(), target.getPathFile());
+ }
+ }
+ }
+}
diff --git a/src/main/java/com/google/devtools/build/lib/sandbox/LinuxAlmostSandboxRunner.java b/src/main/java/com/google/devtools/build/lib/sandbox/LinuxAlmostSandboxRunner.java
deleted file mode 100644
index 73185f3..0000000
--- a/src/main/java/com/google/devtools/build/lib/sandbox/LinuxAlmostSandboxRunner.java
+++ /dev/null
@@ -1,82 +0,0 @@
-// Copyright 2016 The Bazel Authors. All rights reserved.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package com.google.devtools.build.lib.sandbox;
-
-import com.google.devtools.build.lib.runtime.CommandEnvironment;
-import com.google.devtools.build.lib.util.OsUtils;
-import com.google.devtools.build.lib.vfs.FileSystemUtils;
-import com.google.devtools.build.lib.vfs.Path;
-import com.google.devtools.build.lib.vfs.PathFragment;
-import java.io.IOException;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Set;
-
-/**
- * Helper class for running the Linux sandbox. This runner is a subclass of LinuxSandboxRunner which
- * uses process-wrapper instead of linux-sandbox in the same sandbox execution environment.
- */
-public class LinuxAlmostSandboxRunner extends LinuxSandboxRunner {
-
- LinuxAlmostSandboxRunner(
- Path execRoot,
- Path sandboxExecRoot,
- Set<Path> writablePaths,
- List<Path> inaccessiblePaths,
- boolean verboseFailures,
- boolean sandboxDebug) {
- super(
- execRoot, sandboxExecRoot, writablePaths, inaccessiblePaths, verboseFailures, sandboxDebug);
- }
-
- static boolean isSupported(CommandEnvironment commandEnv) {
- PathFragment embeddedTool =
- commandEnv
- .getBlazeWorkspace()
- .getBinTools()
- .getExecPath("process-wrapper" + OsUtils.executableExtension());
- if (embeddedTool == null) {
- // The embedded tool does not exist, meaning that we don't support sandboxing (e.g., while
- // bootstrapping).
- return false;
- }
- return true;
- }
-
- @Override
- protected void runPreparation(int timeout, boolean allowNetwork) throws IOException {
- // Create all needed directories.
- for (Path writablePath : super.getWritablePaths()) {
- if (writablePath.startsWith(super.getSandboxExecRoot())) {
- FileSystemUtils.createDirectoryAndParents(writablePath);
- }
- }
- }
-
- @Override
- protected List<String> runCommandLineArgs(List<String> spawnArguments, int timeout) {
- List<String> commandLineArgs = new ArrayList<>();
-
- commandLineArgs.add(super.getExecRoot().getRelative("_bin/process-wrapper").getPathString());
- commandLineArgs.add(Integer.toString(timeout));
- commandLineArgs.add("5"); /* kill delay: give some time to print stacktraces and whatnot. */
- commandLineArgs.add("-"); /* stdout. */
- commandLineArgs.add("-"); /* stderr. */
-
- commandLineArgs.addAll(spawnArguments);
-
- return commandLineArgs;
- }
-}
diff --git a/src/main/java/com/google/devtools/build/lib/sandbox/LinuxSandboxRunner.java b/src/main/java/com/google/devtools/build/lib/sandbox/LinuxSandboxRunner.java
index 38c4db5..cceed55 100644
--- a/src/main/java/com/google/devtools/build/lib/sandbox/LinuxSandboxRunner.java
+++ b/src/main/java/com/google/devtools/build/lib/sandbox/LinuxSandboxRunner.java
@@ -16,18 +16,10 @@
import com.google.common.collect.ImmutableMap;
import com.google.common.io.ByteStreams;
-import com.google.common.io.Files;
-import com.google.devtools.build.lib.actions.ExecException;
-import com.google.devtools.build.lib.actions.UserExecException;
import com.google.devtools.build.lib.runtime.CommandEnvironment;
-import com.google.devtools.build.lib.shell.AbnormalTerminationException;
import com.google.devtools.build.lib.shell.Command;
import com.google.devtools.build.lib.shell.CommandException;
-import com.google.devtools.build.lib.shell.TerminationStatus;
-import com.google.devtools.build.lib.util.CommandFailureUtils;
import com.google.devtools.build.lib.util.OsUtils;
-import com.google.devtools.build.lib.util.Preconditions;
-import com.google.devtools.build.lib.util.io.FileOutErr;
import com.google.devtools.build.lib.vfs.FileSystemUtils;
import com.google.devtools.build.lib.vfs.Path;
import com.google.devtools.build.lib.vfs.PathFragment;
@@ -35,58 +27,38 @@
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
-import java.util.Collection;
-import java.util.HashSet;
import java.util.List;
import java.util.Map;
-import java.util.Map.Entry;
import java.util.Set;
/**
* Helper class for running the Linux sandbox. This runner prepares environment inside the sandbox,
* handles sandbox output, performs cleanup and changes invocation if necessary.
*/
-public class LinuxSandboxRunner {
+final class LinuxSandboxRunner extends SandboxRunner {
protected static final String LINUX_SANDBOX = "linux-sandbox" + OsUtils.executableExtension();
- protected Path getExecRoot() {
- return execRoot;
- }
-
- protected Path getSandboxExecRoot() {
- return sandboxExecRoot;
- }
-
- protected Set<Path> getWritablePaths() {
- return writablePaths;
- }
-
- protected boolean isVerboseFailures() {
- return verboseFailures;
- }
-
private final Path execRoot;
private final Path sandboxExecRoot;
private final Path argumentsFilePath;
- private final Set<Path> writablePaths;
- private final List<Path> inaccessiblePaths;
- private final boolean verboseFailures;
+ private final Set<Path> writableDirs;
+ private final Set<Path> inaccessiblePaths;
private final boolean sandboxDebug;
LinuxSandboxRunner(
Path execRoot,
+ Path sandboxPath,
Path sandboxExecRoot,
- Set<Path> writablePaths,
- List<Path> inaccessiblePaths,
+ Set<Path> writableDirs,
+ Set<Path> inaccessiblePaths,
boolean verboseFailures,
boolean sandboxDebug) {
+ super(sandboxPath, sandboxExecRoot, verboseFailures);
this.execRoot = execRoot;
this.sandboxExecRoot = sandboxExecRoot;
- this.argumentsFilePath =
- sandboxExecRoot.getParentDirectory().getRelative(sandboxExecRoot.getBaseName() + ".params");
- this.writablePaths = writablePaths;
+ this.argumentsFilePath = sandboxPath.getRelative("linux-sandbox.params");
+ this.writableDirs = writableDirs;
this.inaccessiblePaths = inaccessiblePaths;
- this.verboseFailures = verboseFailures;
this.sandboxDebug = sandboxDebug;
}
@@ -123,9 +95,21 @@
return true;
}
+ @Override
+ protected Command getCommand(
+ List<String> spawnArguments, Map<String, String> env, int timeout, boolean allowNetwork)
+ throws IOException {
+ writeConfig(timeout, allowNetwork);
- protected void runPreparation(int timeout, boolean allowNetwork) throws IOException {
+ List<String> commandLineArgs = new ArrayList<>(3 + spawnArguments.size());
+ commandLineArgs.add(execRoot.getRelative("_bin/linux-sandbox").getPathString());
+ commandLineArgs.add("@" + argumentsFilePath.getPathString());
+ commandLineArgs.add("--");
+ commandLineArgs.addAll(spawnArguments);
+ return new Command(commandLineArgs.toArray(new String[0]), env, sandboxExecRoot.getPathFile());
+ }
+ private void writeConfig(int timeout, boolean allowNetwork) throws IOException {
List<String> fileArgs = new ArrayList<>();
if (sandboxDebug) {
@@ -143,12 +127,9 @@
}
// Create all needed directories.
- for (Path writablePath : writablePaths) {
+ for (Path writablePath : writableDirs) {
fileArgs.add("-w");
fileArgs.add(writablePath.getPathString());
- if (writablePath.startsWith(sandboxExecRoot)) {
- FileSystemUtils.createDirectoryAndParents(writablePath);
- }
}
for (Path inaccessiblePath : inaccessiblePaths) {
@@ -163,134 +144,4 @@
FileSystemUtils.writeLinesAs(argumentsFilePath, StandardCharsets.ISO_8859_1, fileArgs);
}
-
- protected List<String> runCommandLineArgs(List<String> spawnArguments, int timeout) {
- List<String> commandLineArgs = new ArrayList<>();
-
- commandLineArgs.add(execRoot.getRelative("_bin/linux-sandbox").getPathString());
-
- commandLineArgs.add("@" + argumentsFilePath.getPathString());
-
- commandLineArgs.add("--");
- commandLineArgs.addAll(spawnArguments);
-
- return commandLineArgs;
- }
-
- /**
- * Runs given
- *
- * @param spawnArguments - arguments of spawn to run inside the sandbox
- * @param env - environment to run sandbox in
- * @param outErr - error output to capture sandbox's and command's stderr
- * @param outputs - files to extract from the sandbox, paths are relative to the exec root @throws
- * ExecException
- */
- public void run(
- List<String> spawnArguments,
- Map<String, String> env,
- FileOutErr outErr,
- Map<PathFragment, Path> inputs,
- Collection<PathFragment> outputs,
- int timeout,
- boolean allowNetwork)
- throws IOException, ExecException {
- createFileSystem(inputs, outputs);
-
- runPreparation(timeout, allowNetwork);
-
- List<String> commandLineArgs = runCommandLineArgs(spawnArguments, timeout);
- Command cmd =
- new Command(commandLineArgs.toArray(new String[0]), env, sandboxExecRoot.getPathFile());
-
- try {
- cmd.execute(
- /* stdin */ new byte[] {},
- Command.NO_OBSERVER,
- outErr.getOutputStream(),
- outErr.getErrorStream(),
- /* killSubprocessOnInterrupt */ true);
- } catch (CommandException e) {
- boolean timedOut = false;
- if (e instanceof AbnormalTerminationException) {
- TerminationStatus status =
- ((AbnormalTerminationException) e).getResult().getTerminationStatus();
- timedOut = !status.exited() && (status.getTerminatingSignal() == 14 /* SIGALRM */);
- }
- String message =
- CommandFailureUtils.describeCommandFailure(
- verboseFailures, commandLineArgs, env, sandboxExecRoot.getPathString());
- throw new UserExecException(message, e, timedOut);
- } finally {
- copyOutputs(outputs);
- }
- }
-
- protected void createFileSystem(Map<PathFragment, Path> inputs, Collection<PathFragment> outputs)
- throws IOException {
- Set<Path> createdDirs = new HashSet<>();
- FileSystemUtils.createDirectoryAndParentsWithCache(createdDirs, sandboxExecRoot);
- createParentDirectoriesForInputs(createdDirs, inputs.keySet());
- createSymlinksForInputs(inputs);
- createDirectoriesForOutputs(createdDirs, outputs);
- }
-
- /**
- * No input can be a child of another input, because otherwise we might try to create a symlink
- * below another symlink we created earlier - which means we'd actually end up writing somewhere
- * in the workspace.
- *
- * <p>If all inputs were regular files, this situation could naturally not happen - but
- * unfortunately, we might get the occasional action that has directories in its inputs.
- *
- * <p>Creating all parent directories first ensures that we can safely create symlinks to
- * directories, too, because we'll get an IOException with EEXIST if inputs happen to be nested
- * once we start creating the symlinks for all inputs.
- */
- private void createParentDirectoriesForInputs(Set<Path> createdDirs, Set<PathFragment> inputs)
- throws IOException {
- for (PathFragment inputPath : inputs) {
- Path dir = sandboxExecRoot.getRelative(inputPath).getParentDirectory();
- Preconditions.checkArgument(dir.startsWith(sandboxExecRoot));
- FileSystemUtils.createDirectoryAndParentsWithCache(createdDirs, dir);
- }
- }
-
- private void createSymlinksForInputs(Map<PathFragment, Path> inputs) throws IOException {
- // All input files are relative to the execroot.
- for (Entry<PathFragment, Path> entry : inputs.entrySet()) {
- Path key = sandboxExecRoot.getRelative(entry.getKey());
- key.createSymbolicLink(entry.getValue());
- }
- }
-
- /** Prepare the output directories in the sandbox. */
- private void createDirectoriesForOutputs(Set<Path> createdDirs, Collection<PathFragment> outputs)
- throws IOException {
- for (PathFragment output : outputs) {
- FileSystemUtils.createDirectoryAndParentsWithCache(
- createdDirs, sandboxExecRoot.getRelative(output.getParentDirectory()));
- FileSystemUtils.createDirectoryAndParentsWithCache(
- createdDirs, execRoot.getRelative(output.getParentDirectory()));
- }
- }
-
- protected void copyOutputs(Collection<PathFragment> outputs) throws IOException {
- for (PathFragment output : outputs) {
- Path source = sandboxExecRoot.getRelative(output);
- if (source.isFile() || source.isSymbolicLink()) {
- Path target = execRoot.getRelative(output);
- Files.move(source.getPathFile(), target.getPathFile());
- }
- }
- }
-
- public void cleanup() throws IOException {
- if (sandboxExecRoot.exists()) {
- FileSystemUtils.deleteTree(sandboxExecRoot);
- }
- if (argumentsFilePath.exists()) {
- argumentsFilePath.delete();
- }
- }
}
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 962d24a..f984af7 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
@@ -11,16 +11,11 @@
// 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.ImmutableList;
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;
@@ -28,60 +23,48 @@
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.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.runtime.CommandEnvironment;
-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.FileSystem;
import com.google.devtools.build.lib.vfs.Path;
import com.google.devtools.build.lib.vfs.PathFragment;
-import java.io.File;
import java.io.IOException;
-import java.nio.charset.StandardCharsets;
import java.util.HashMap;
-import java.util.List;
import java.util.Map;
+import java.util.Set;
import java.util.UUID;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.atomic.AtomicInteger;
-/**
- * Strategy that uses sandboxing to execute a process.
- */
+/** Strategy that uses sandboxing to execute a process. */
@ExecutionStrategy(
name = {"sandboxed"},
contextType = SpawnActionContext.class
)
-public class LinuxSandboxedStrategy implements SpawnActionContext {
+public class LinuxSandboxedStrategy extends SandboxStrategy {
private static Boolean sandboxingSupported = null;
public static boolean isSupported(CommandEnvironment env) {
if (sandboxingSupported == null) {
- // Currently LinuxSandboxRunner support <= LinuxAlmostSandboxRunner support
sandboxingSupported =
- LinuxAlmostSandboxRunner.isSupported(env) || LinuxSandboxRunner.isSupported(env);
+ ProcessWrapperRunner.isSupported(env) || LinuxSandboxRunner.isSupported(env);
}
return sandboxingSupported.booleanValue();
}
- private final ExecutorService backgroundWorkers;
-
private final BuildRequest buildRequest;
private final SandboxOptions sandboxOptions;
private final BlazeDirectories blazeDirs;
private final Path execRoot;
+ private final ExecutorService backgroundWorkers;
private final boolean verboseFailures;
- private final UUID uuid = UUID.randomUUID();
- private final AtomicInteger execCounter = new AtomicInteger();
private final String productName;
private final boolean fullySupported;
+ private final UUID uuid = UUID.randomUUID();
+ private final AtomicInteger execCounter = new AtomicInteger();
+
LinuxSandboxedStrategy(
BuildRequest buildRequest,
BlazeDirectories blazeDirs,
@@ -89,6 +72,7 @@
boolean verboseFailures,
String productName,
boolean fullySupported) {
+ super(blazeDirs, verboseFailures, buildRequest.getOptions(SandboxOptions.class));
this.buildRequest = buildRequest;
this.sandboxOptions = buildRequest.getOptions(SandboxOptions.class);
this.blazeDirs = blazeDirs;
@@ -109,317 +93,69 @@
// Certain actions can't run remotely or in a sandbox - pass them on to the standalone strategy.
if (!spawn.isRemotable()) {
- StandaloneSpawnStrategy standaloneStrategy =
- Preconditions.checkNotNull(executor.getContext(StandaloneSpawnStrategy.class));
- 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 sandboxExecRoot =
- blazeDirs.getOutputBase().getRelative(productName + "-sandbox").getRelative(execId);
+ Path sandboxPath = SandboxHelpers.getSandboxRoot(blazeDirs, productName, uuid, execCounter);
+ Path sandboxExecRoot = sandboxPath.getRelative("execroot");
- // Gather all necessary mounts for the sandbox.
- Map<PathFragment, Path> mounts;
- try {
- mounts = getMounts(spawn, actionExecutionContext);
- } catch (IllegalArgumentException | IOException e) {
- throw new EnvironmentalExecException("Could not prepare mounts for sandbox execution", e);
- }
-
- Map<String, String> env = new HashMap<>(spawn.getEnvironment());
-
- ImmutableSet<Path> writablePaths = getWritablePaths(sandboxExecRoot, env);
- ImmutableList<Path> inaccessiblePaths = getInaccessiblePaths();
-
- int timeout = getTimeout(spawn);
-
- ImmutableSet.Builder<PathFragment> outputFiles = ImmutableSet.builder();
- for (PathFragment optionalOutput : spawn.getOptionalOutputFiles()) {
- Preconditions.checkArgument(!optionalOutput.isAbsolute());
- outputFiles.add(optionalOutput);
- }
- for (ActionInput output : spawn.getOutputFiles()) {
- outputFiles.add(new PathFragment(output.getExecPathString()));
- }
+ Set<Path> writableDirs = getWritableDirs(sandboxExecRoot, spawn.getEnvironment());
try {
- final LinuxSandboxRunner runner;
+ // Build the execRoot for the sandbox.
+ SymlinkedExecRoot symlinkedExecRoot = new SymlinkedExecRoot(sandboxExecRoot);
+ ImmutableSet<PathFragment> outputs = SandboxHelpers.getOutputFiles(spawn);
+ symlinkedExecRoot.createFileSystem(
+ getMounts(spawn, actionExecutionContext), outputs, writableDirs);
+
+ final SandboxRunner runner;
if (fullySupported) {
runner =
new LinuxSandboxRunner(
execRoot,
+ sandboxPath,
sandboxExecRoot,
- writablePaths,
- inaccessiblePaths,
+ getWritableDirs(sandboxExecRoot, spawn.getEnvironment()),
+ getInaccessiblePaths(),
verboseFailures,
sandboxOptions.sandboxDebug);
} else {
- // Then LinuxAlmostSandboxRunner must be supported
- runner =
- new LinuxAlmostSandboxRunner(
- execRoot,
- sandboxExecRoot,
- writablePaths,
- inaccessiblePaths,
- verboseFailures,
- sandboxOptions.sandboxDebug);
+ runner = new ProcessWrapperRunner(execRoot, sandboxPath, sandboxExecRoot, verboseFailures);
}
try {
runner.run(
spawn.getArguments(),
- env,
- outErr,
- mounts,
- outputFiles.build(),
- timeout,
+ spawn.getEnvironment(),
+ actionExecutionContext.getFileOutErr(),
+ SandboxHelpers.getTimeout(spawn),
SandboxHelpers.shouldAllowNetwork(buildRequest, spawn));
} finally {
- // Due to the Linux kernel behavior, if we try to remove the sandbox too quickly after the
- // process has exited, we get "Device busy" errors because some of the mounts have not yet
- // been undone. A second later it usually works. We will just clean the old sandboxes up
- // using a background worker.
- 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.
- }
- }
- });
+ symlinkedExecRoot.copyOutputs(execRoot, outputs);
+ if (!sandboxOptions.sandboxDebug) {
+ SandboxHelpers.lazyCleanup(backgroundWorkers, runner);
+ }
}
} catch (IOException e) {
throw new UserExecException("I/O error during sandboxed execution", e);
}
}
- 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;
- }
-
- /** Gets the list of directories that the spawn will assume to be writable. */
- private ImmutableSet<Path> getWritablePaths(Path sandboxExecRoot, Map<String, String> env) {
- ImmutableSet.Builder<Path> writablePaths = ImmutableSet.builder();
- // We have to make the TEST_TMPDIR directory writable if it is specified.
- if (env.containsKey("TEST_TMPDIR")) {
- Path testTmpDir = sandboxExecRoot.getRelative(env.get("TEST_TMPDIR"));
- writablePaths.add(testTmpDir);
- env.put("TEST_TMPDIR", testTmpDir.getPathString());
- }
- return writablePaths.build();
- }
-
- private ImmutableList<Path> getInaccessiblePaths() {
- ImmutableList.Builder<Path> inaccessiblePaths = ImmutableList.builder();
- for (String path : sandboxOptions.sandboxBlockPath) {
- inaccessiblePaths.add(blazeDirs.getFileSystem().getPath(path));
- }
- return inaccessiblePaths.build();
- }
-
private Map<PathFragment, Path> getMounts(Spawn spawn, ActionExecutionContext executionContext)
- throws IOException, ExecException {
- Map<PathFragment, Path> mounts = new HashMap<>();
- mountRunfilesFromManifests(mounts, spawn);
- mountRunfilesFromSuppliers(mounts, spawn);
- mountFilesFromFilesetManifests(mounts, spawn, executionContext);
- mountInputs(mounts, spawn, executionContext);
- return mounts;
- }
-
- /** Mount all runfiles that the spawn needs as specified in its runfiles manifests. */
- private void mountRunfilesFromManifests(Map<PathFragment, Path> mounts, Spawn spawn)
- throws IOException, ExecException {
- for (Map.Entry<PathFragment, Artifact> manifest : spawn.getRunfilesManifests().entrySet()) {
- String manifestFilePath = manifest.getValue().getPath().getPathString();
- Preconditions.checkState(!manifest.getKey().isAbsolute());
- PathFragment targetDirectory = manifest.getKey();
-
- parseManifestFile(
- blazeDirs.getFileSystem(),
- mounts,
- targetDirectory,
- new File(manifestFilePath),
- false,
- "");
+ throws ExecException {
+ try {
+ Map<PathFragment, Path> mounts = new HashMap<>();
+ mountRunfilesFromManifests(mounts, spawn);
+ mountRunfilesFromSuppliers(mounts, spawn);
+ mountFilesFromFilesetManifests(mounts, spawn, executionContext);
+ mountInputs(mounts, spawn, executionContext);
+ return mounts;
+ } catch (IllegalArgumentException | IOException e) {
+ throw new EnvironmentalExecException("Could not prepare mounts for sandbox execution", e);
}
}
-
- /** Mount all files that the spawn needs as specified in its fileset manifests. */
- private void mountFilesFromFilesetManifests(
- Map<PathFragment, Path> mounts, Spawn spawn, ActionExecutionContext executionContext)
- throws IOException, ExecException {
- final FilesetActionContext filesetContext =
- executionContext.getExecutor().getContext(FilesetActionContext.class);
- for (Artifact fileset : spawn.getFilesetManifests()) {
- File manifestFile =
- new File(
- execRoot.getPathString(),
- AnalysisUtils.getManifestPathFromFilesetPath(fileset.getExecPath()).getPathString());
- PathFragment targetDirectory = fileset.getExecPath();
-
- parseManifestFile(
- blazeDirs.getFileSystem(),
- mounts,
- targetDirectory,
- manifestFile,
- true,
- filesetContext.getWorkspaceName());
- }
- }
-
- /** A parser for the MANIFEST files used by Filesets and runfiles. */
- static void parseManifestFile(
- FileSystem fs,
- Map<PathFragment, Path> mounts,
- PathFragment targetDirectory,
- File manifestFile,
- boolean isFilesetManifest,
- String workspaceName)
- throws IOException, ExecException {
- 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(" ");
-
- // The "target" field is always a relative path that is to be interpreted in this way:
- // (1) If this is a fileset manifest and our workspace name is not empty, the first segment
- // of each "target" path must be the workspace name, which is then stripped before further
- // processing.
- // (2) The "target" path is then appended to the "targetDirectory", which is a path relative
- // to the execRoot. Together, this results in the full path in the execRoot in which place a
- // symlink referring to "source" has to be created (see below).
- 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]);
- }
-
- // The "source" field, if it exists, is always an absolute path and may point to any file in
- // the filesystem (it is not limited to files in the workspace or execroot).
- Path source;
- switch (fields.length) {
- case 1:
- source = fs.getPath("/dev/null");
- break;
- case 2:
- source = fs.getPath(fields[1]);
- break;
- default:
- throw new IllegalStateException("'" + line + "' splits into more than 2 parts");
- }
-
- mounts.put(targetPath, source);
- }
- }
-
- /** Mount all runfiles that the spawn needs as specified via its runfiles suppliers. */
- private void mountRunfilesFromSuppliers(Map<PathFragment, Path> mounts, Spawn spawn)
- throws IOException {
- Map<PathFragment, Map<PathFragment, Artifact>> rootsAndMappings =
- spawn.getRunfilesSupplier().getMappings();
- for (Map.Entry<PathFragment, Map<PathFragment, Artifact>> rootAndMappings :
- rootsAndMappings.entrySet()) {
- PathFragment root = rootAndMappings.getKey();
- if (root.isAbsolute()) {
- root = root.relativeTo(execRoot.asFragment());
- }
- for (Map.Entry<PathFragment, Artifact> mapping : rootAndMappings.getValue().entrySet()) {
- Artifact sourceArtifact = mapping.getValue();
- PathFragment source =
- (sourceArtifact != null) ? sourceArtifact.getExecPath() : new PathFragment("/dev/null");
-
- Preconditions.checkArgument(!mapping.getKey().isAbsolute());
- PathFragment target = root.getRelative(mapping.getKey());
- mounts.put(target, execRoot.getRelative(source));
- }
- }
- }
-
- /** Mount all inputs of the spawn. */
- private void mountInputs(
- Map<PathFragment, Path> mounts, Spawn spawn, ActionExecutionContext actionExecutionContext) {
- 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;
- }
- PathFragment mount = new PathFragment(input.getExecPathString());
- mounts.put(mount, execRoot.getRelative(mount));
- }
- }
-
- @Override
- public boolean willExecuteRemotely(boolean remotable) {
- return false;
- }
-
- @Override
- public String toString() {
- return "sandboxed";
- }
-
- @Override
- public boolean shouldPropagateExecException() {
- return verboseFailures && sandboxOptions.sandboxDebug;
- }
}
diff --git a/src/main/java/com/google/devtools/build/lib/sandbox/ProcessWrapperRunner.java b/src/main/java/com/google/devtools/build/lib/sandbox/ProcessWrapperRunner.java
new file mode 100644
index 0000000..0122b64
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/sandbox/ProcessWrapperRunner.java
@@ -0,0 +1,68 @@
+// Copyright 2016 The Bazel Authors. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.devtools.build.lib.sandbox;
+
+import com.google.devtools.build.lib.runtime.CommandEnvironment;
+import com.google.devtools.build.lib.shell.Command;
+import com.google.devtools.build.lib.util.OsUtils;
+import com.google.devtools.build.lib.vfs.Path;
+import com.google.devtools.build.lib.vfs.PathFragment;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * This runner runs process-wrapper inside a sandboxed execution root, which should work on most
+ * platforms and gives at least some isolation between running actions.
+ */
+final class ProcessWrapperRunner extends SandboxRunner {
+ private final Path execRoot;
+ private final Path sandboxExecRoot;
+
+ ProcessWrapperRunner(
+ Path execRoot, Path sandboxPath, Path sandboxExecRoot, boolean verboseFailures) {
+ super(sandboxPath, sandboxExecRoot, verboseFailures);
+ this.execRoot = execRoot;
+ this.sandboxExecRoot = sandboxExecRoot;
+ }
+
+ static boolean isSupported(CommandEnvironment commandEnv) {
+ PathFragment embeddedTool =
+ commandEnv
+ .getBlazeWorkspace()
+ .getBinTools()
+ .getExecPath("process-wrapper" + OsUtils.executableExtension());
+ if (embeddedTool == null) {
+ // The embedded tool does not exist, meaning that we don't support sandboxing (e.g., while
+ // bootstrapping).
+ return false;
+ }
+ return true;
+ }
+
+ @Override
+ protected Command getCommand(
+ List<String> spawnArguments, Map<String, String> env, int timeout, boolean allowNetwork) {
+ List<String> commandLineArgs = new ArrayList<>(5 + spawnArguments.size());
+ commandLineArgs.add(execRoot.getRelative("_bin/process-wrapper").getPathString());
+ commandLineArgs.add(Integer.toString(timeout));
+ commandLineArgs.add("5"); /* kill delay: give some time to print stacktraces and whatnot. */
+ commandLineArgs.add("-"); /* stdout. */
+ commandLineArgs.add("-"); /* stderr. */
+ commandLineArgs.addAll(spawnArguments);
+
+ return new Command(commandLineArgs.toArray(new String[0]), env, sandboxExecRoot.getPathFile());
+ }
+}
diff --git a/src/main/java/com/google/devtools/build/lib/sandbox/SandboxActionContextConsumer.java b/src/main/java/com/google/devtools/build/lib/sandbox/SandboxActionContextConsumer.java
index 83edc09..cd468fd 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
@@ -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 com.google.common.collect.ImmutableMap;
@@ -26,7 +27,7 @@
* {@link ActionContextConsumer} that requests the action contexts necessary for sandboxed
* execution.
*/
-public class SandboxActionContextConsumer implements ActionContextConsumer {
+final class SandboxActionContextConsumer implements ActionContextConsumer {
private final ImmutableMultimap<Class<? extends ActionContext>, String> contexts;
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 0c7d7aa..8153f1d 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
@@ -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 com.google.common.collect.ImmutableList;
@@ -27,7 +28,7 @@
/**
* Provides the sandboxed spawn strategy.
*/
-public class SandboxActionContextProvider extends ActionContextProvider {
+final class SandboxActionContextProvider extends ActionContextProvider {
public static final String SANDBOX_NOT_SUPPORTED_MESSAGE =
"Sandboxed execution is not supported on your system and thus hermeticity of actions cannot "
diff --git a/src/main/java/com/google/devtools/build/lib/sandbox/SandboxExecRoot.java b/src/main/java/com/google/devtools/build/lib/sandbox/SandboxExecRoot.java
new file mode 100644
index 0000000..0c392fd
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/sandbox/SandboxExecRoot.java
@@ -0,0 +1,56 @@
+// Copyright 2016 The Bazel Authors. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.devtools.build.lib.sandbox;
+
+import com.google.devtools.build.lib.vfs.Path;
+import com.google.devtools.build.lib.vfs.PathFragment;
+import java.io.IOException;
+import java.util.Collection;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * SandboxExecRoot is responsible for making a list of input files available inside the directory,
+ * so that a process running inside the directory can access the files. It also handles moving the
+ * output files generated by the process out of the directory into a destination directory.
+ */
+public interface SandboxExecRoot {
+
+ /**
+ * Creates the sandboxed execution root, making all {@code inputs} available for reading, making
+ * sure that the parent directories of all {@code outputs} and that all {@code writableDirs}
+ * exist and can be written into.
+ *
+ * @param inputs Specifies the input files to be made available inside the directory. The key of
+ * the map is a relative path inside the sandboxed execution root, while the value is the
+ * absolute path of the file in the filesystem.
+ * @param outputs Output files that the process is expected to write to as relative paths to the
+ * sandboxed execution root.
+ * @param writableDirs Directories that the process may write into. All paths that are not inside
+ * the sandboxed execution root must be ignored by this method.
+ * @throws IOException
+ */
+ void createFileSystem(
+ Map<PathFragment, Path> inputs, Collection<PathFragment> outputs, Set<Path> writableDirs)
+ throws IOException;
+
+ /**
+ * Moves all {@code outputs} to {@code execRoot} while keeping the directory structure.
+ *
+ * @throws IOException
+ */
+ void copyOutputs(Path execRoot, Collection<PathFragment> outputs) throws IOException;
+
+}
diff --git a/src/main/java/com/google/devtools/build/lib/sandbox/SandboxHelpers.java b/src/main/java/com/google/devtools/build/lib/sandbox/SandboxHelpers.java
index bd8c365..391748f 100644
--- a/src/main/java/com/google/devtools/build/lib/sandbox/SandboxHelpers.java
+++ b/src/main/java/com/google/devtools/build/lib/sandbox/SandboxHelpers.java
@@ -4,22 +4,109 @@
// 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
+// 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.ImmutableSet;
+import com.google.common.collect.ImmutableSet.Builder;
+import com.google.devtools.build.lib.actions.ActionExecutionContext;
+import com.google.devtools.build.lib.actions.ActionInput;
+import com.google.devtools.build.lib.actions.ActionStatusMessage;
+import com.google.devtools.build.lib.actions.ExecException;
+import com.google.devtools.build.lib.actions.Executor;
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.analysis.config.BuildConfiguration;
import com.google.devtools.build.lib.buildtool.BuildRequest;
+import com.google.devtools.build.lib.cmdline.Label;
+import com.google.devtools.build.lib.standalone.StandaloneSpawnStrategy;
+import com.google.devtools.build.lib.util.Preconditions;
+import com.google.devtools.build.lib.vfs.Path;
+import com.google.devtools.build.lib.vfs.PathFragment;
+import java.io.IOException;
+import java.util.UUID;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.atomic.AtomicInteger;
/** Helper methods that are shared by the different sandboxing strategies in this package. */
final class SandboxHelpers {
+ static void lazyCleanup(ExecutorService backgroundWorkers, final SandboxRunner runner) {
+ // 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) {
+ // Mark ourselves as interrupted and then exit.
+ Thread.currentThread().interrupt();
+ }
+ }
+ });
+ }
+
+ static void fallbackToNonSandboxedExecution(
+ Spawn spawn, ActionExecutionContext actionExecutionContext, Executor executor)
+ throws ExecException {
+ StandaloneSpawnStrategy standaloneStrategy =
+ Preconditions.checkNotNull(executor.getContext(StandaloneSpawnStrategy.class));
+ standaloneStrategy.exec(spawn, actionExecutionContext);
+ }
+
+ static void reportSubcommand(Executor executor, Spawn spawn) {
+ if (executor.reportsSubcommands()) {
+ executor.reportSubcommand(
+ Label.print(spawn.getOwner().getLabel())
+ + " ["
+ + spawn.getResourceOwner().prettyPrint()
+ + "]",
+ spawn.asShellCommand(executor.getExecRoot()));
+ }
+ }
+
+ static 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;
+ }
+
+ static ImmutableSet<PathFragment> getOutputFiles(Spawn spawn) {
+ Builder<PathFragment> outputFiles = ImmutableSet.builder();
+ for (PathFragment optionalOutput : spawn.getOptionalOutputFiles()) {
+ Preconditions.checkArgument(!optionalOutput.isAbsolute());
+ outputFiles.add(optionalOutput);
+ }
+ for (ActionInput output : spawn.getOutputFiles()) {
+ outputFiles.add(new PathFragment(output.getExecPathString()));
+ }
+ return outputFiles.build();
+ }
+
static boolean shouldAllowNetwork(BuildRequest buildRequest, Spawn spawn) {
// If we don't run tests, allow network access.
if (!buildRequest.shouldRunTests()) {
@@ -42,4 +129,18 @@
return false;
}
+
+ static void postActionStatusMessage(Executor executor, Spawn spawn) {
+ executor
+ .getEventBus()
+ .post(ActionStatusMessage.runningStrategy(spawn.getResourceOwner(), "sandbox"));
+ }
+
+ static Path getSandboxRoot(
+ BlazeDirectories blazeDirs, String productName, UUID uuid, AtomicInteger execCounter) {
+ return blazeDirs
+ .getOutputBase()
+ .getRelative(productName + "-sandbox")
+ .getRelative(uuid + "-" + execCounter.getAndIncrement());
+ }
}
diff --git a/src/main/java/com/google/devtools/build/lib/sandbox/SandboxModule.java b/src/main/java/com/google/devtools/build/lib/sandbox/SandboxModule.java
index 6b54a87..53c5c67 100644
--- a/src/main/java/com/google/devtools/build/lib/sandbox/SandboxModule.java
+++ b/src/main/java/com/google/devtools/build/lib/sandbox/SandboxModule.java
@@ -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 com.google.common.collect.ImmutableList;
@@ -33,7 +34,7 @@
/**
* This module provides the Sandbox spawn strategy.
*/
-public class SandboxModule extends BlazeModule {
+public final class SandboxModule extends BlazeModule {
// Per-server state
private ExecutorService backgroundWorkers;
@@ -69,9 +70,9 @@
@Override
public void beforeCommand(Command command, CommandEnvironment env) {
- backgroundWorkers = Executors.newCachedThreadPool(new ThreadFactoryBuilder()
- .setNameFormat("linux-sandbox-background-worker-%d")
- .build());
+ backgroundWorkers =
+ Executors.newCachedThreadPool(
+ new ThreadFactoryBuilder().setNameFormat("sandbox-background-worker-%d").build());
this.env = env;
env.getEventBus().register(this);
}
diff --git a/src/main/java/com/google/devtools/build/lib/sandbox/SandboxOptions.java b/src/main/java/com/google/devtools/build/lib/sandbox/SandboxOptions.java
index f18792f..00ac908 100644
--- a/src/main/java/com/google/devtools/build/lib/sandbox/SandboxOptions.java
+++ b/src/main/java/com/google/devtools/build/lib/sandbox/SandboxOptions.java
@@ -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 com.google.devtools.common.options.Option;
diff --git a/src/main/java/com/google/devtools/build/lib/sandbox/SandboxRunner.java b/src/main/java/com/google/devtools/build/lib/sandbox/SandboxRunner.java
new file mode 100644
index 0000000..bd9d09f
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/sandbox/SandboxRunner.java
@@ -0,0 +1,120 @@
+// Copyright 2016 The Bazel Authors. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.devtools.build.lib.sandbox;
+
+import com.google.devtools.build.lib.actions.ExecException;
+import com.google.devtools.build.lib.actions.UserExecException;
+import com.google.devtools.build.lib.shell.AbnormalTerminationException;
+import com.google.devtools.build.lib.shell.Command;
+import com.google.devtools.build.lib.shell.CommandException;
+import com.google.devtools.build.lib.shell.KillableObserver;
+import com.google.devtools.build.lib.shell.TerminationStatus;
+import com.google.devtools.build.lib.util.CommandFailureUtils;
+import com.google.devtools.build.lib.util.io.OutErr;
+import com.google.devtools.build.lib.vfs.FileSystemUtils;
+import com.google.devtools.build.lib.vfs.Path;
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Map;
+
+/** A common interface of all sandbox runners, no matter which platform they're working on. */
+abstract class SandboxRunner {
+ private final Path sandboxPath;
+ private final boolean verboseFailures;
+ private final Path sandboxExecRoot;
+
+ SandboxRunner(Path sandboxPath, Path sandboxExecRoot, boolean verboseFailures) {
+ this.sandboxPath = sandboxPath;
+ this.sandboxExecRoot = sandboxExecRoot;
+ this.verboseFailures = verboseFailures;
+ }
+
+ /**
+ * Runs the command specified via {@code arguments} and {@code env} inside the sandbox.
+ *
+ * @param arguments - arguments of spawn to run inside the sandbox.
+ * @param environment - environment variables to pass to the spawn.
+ * @param outErr - error output to capture sandbox's and command's stderr
+ * @param timeout - after how many seconds should the process be killed
+ * @param allowNetwork - whether networking should be allowed for the process
+ */
+ void run(
+ List<String> arguments,
+ Map<String, String> environment,
+ OutErr outErr,
+ int timeout,
+ boolean allowNetwork)
+ throws IOException, ExecException {
+ Command cmd = getCommand(arguments, environment, timeout, allowNetwork);
+
+ try {
+ cmd.execute(
+ /* stdin */ new byte[] {},
+ getCommandObserver(timeout),
+ outErr.getOutputStream(),
+ outErr.getErrorStream(),
+ /* killSubprocessOnInterrupt */ true);
+ } catch (CommandException e) {
+ boolean timedOut = false;
+ if (e instanceof AbnormalTerminationException) {
+ TerminationStatus status =
+ ((AbnormalTerminationException) e).getResult().getTerminationStatus();
+ timedOut = !status.exited() && (status.getTerminatingSignal() == getSignalOnTimeout());
+ }
+ String message =
+ CommandFailureUtils.describeCommandFailure(
+ verboseFailures,
+ Arrays.asList(cmd.getCommandLineElements()),
+ environment,
+ sandboxExecRoot.getPathString());
+ throw new UserExecException(message, e, timedOut);
+ }
+ }
+
+ /**
+ * Returns the {@link Command} that the {@link #run} method will execute inside the sandbox.
+ *
+ * @param arguments - arguments of spawn to run inside the sandbox.
+ * @param environment - environment variables to pass to the spawn.
+ * @param timeout - after how many seconds should the process be killed
+ * @param allowNetwork - whether networking should be allowed for the process
+ */
+ protected abstract Command getCommand(
+ List<String> arguments, Map<String, String> environment, int timeout, boolean allowNetwork)
+ throws IOException;
+
+ /**
+ * Returns a {@link KillableObserver} that the {@link #run} method will use when executing the
+ * command returned by {@link #getCommand}.
+ */
+ protected KillableObserver getCommandObserver(int timeout) {
+ return Command.NO_OBSERVER;
+ }
+
+ /**
+ * Returns the signal code that the command returned by {@link #getCommand} exits with in case of
+ * a timeout.
+ */
+ protected int getSignalOnTimeout() {
+ return 14; /* SIGALRM */
+ }
+
+ void cleanup() throws IOException {
+ if (sandboxPath.exists()) {
+ FileSystemUtils.deleteTree(sandboxPath);
+ }
+ }
+}
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
new file mode 100644
index 0000000..645e669
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/sandbox/SandboxStrategy.java
@@ -0,0 +1,237 @@
+// Copyright 2016 The Bazel Authors. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.devtools.build.lib.sandbox;
+
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.ImmutableSet.Builder;
+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.Artifact;
+import com.google.devtools.build.lib.actions.EnvironmentalExecException;
+import com.google.devtools.build.lib.actions.ExecException;
+import com.google.devtools.build.lib.actions.Spawn;
+import com.google.devtools.build.lib.actions.SpawnActionContext;
+import com.google.devtools.build.lib.analysis.AnalysisUtils;
+import com.google.devtools.build.lib.analysis.BlazeDirectories;
+import com.google.devtools.build.lib.rules.cpp.CppCompileAction;
+import com.google.devtools.build.lib.rules.fileset.FilesetActionContext;
+import com.google.devtools.build.lib.util.Preconditions;
+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.File;
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.util.List;
+import java.util.Map;
+
+/** Abstract common ancestor for sandbox strategies implementing the common parts. */
+abstract class SandboxStrategy implements SpawnActionContext {
+
+ private final BlazeDirectories blazeDirs;
+ private final Path execRoot;
+ private final boolean verboseFailures;
+ private final SandboxOptions sandboxOptions;
+
+ public SandboxStrategy(
+ BlazeDirectories blazeDirs, boolean verboseFailures, SandboxOptions sandboxOptions) {
+ this.blazeDirs = blazeDirs;
+ this.execRoot = blazeDirs.getExecRoot();
+ this.verboseFailures = verboseFailures;
+ this.sandboxOptions = sandboxOptions;
+ }
+
+ /** Gets the list of directories that the spawn will assume to be writable. */
+ protected ImmutableSet<Path> getWritableDirs(Path sandboxExecRoot, Map<String, String> env) {
+ Builder<Path> writableDirs = ImmutableSet.builder();
+ // We have to make the TEST_TMPDIR directory writable if it is specified.
+ if (env.containsKey("TEST_TMPDIR")) {
+ writableDirs.add(sandboxExecRoot.getRelative(env.get("TEST_TMPDIR")));
+ }
+ return writableDirs.build();
+ }
+
+ protected ImmutableSet<Path> getInaccessiblePaths() {
+ ImmutableSet.Builder<Path> inaccessiblePaths = ImmutableSet.builder();
+ for (String path : sandboxOptions.sandboxBlockPath) {
+ inaccessiblePaths.add(blazeDirs.getFileSystem().getPath(path));
+ }
+ return inaccessiblePaths.build();
+ }
+
+ /** Mount all runfiles that the spawn needs as specified in its runfiles manifests. */
+ protected void mountRunfilesFromManifests(Map<PathFragment, Path> mounts, Spawn spawn)
+ throws IOException, ExecException {
+ for (Map.Entry<PathFragment, Artifact> manifest : spawn.getRunfilesManifests().entrySet()) {
+ String manifestFilePath = manifest.getValue().getPath().getPathString();
+ Preconditions.checkState(!manifest.getKey().isAbsolute());
+ PathFragment targetDirectory = manifest.getKey();
+
+ parseManifestFile(
+ blazeDirs.getFileSystem(),
+ mounts,
+ targetDirectory,
+ new File(manifestFilePath),
+ false,
+ "");
+ }
+ }
+
+ /** Mount all files that the spawn needs as specified in its fileset manifests. */
+ protected void mountFilesFromFilesetManifests(
+ Map<PathFragment, Path> mounts, Spawn spawn, ActionExecutionContext executionContext)
+ throws IOException, ExecException {
+ final FilesetActionContext filesetContext =
+ executionContext.getExecutor().getContext(FilesetActionContext.class);
+ for (Artifact fileset : spawn.getFilesetManifests()) {
+ File manifestFile =
+ new File(
+ execRoot.getPathString(),
+ AnalysisUtils.getManifestPathFromFilesetPath(fileset.getExecPath()).getPathString());
+ PathFragment targetDirectory = fileset.getExecPath();
+
+ parseManifestFile(
+ blazeDirs.getFileSystem(),
+ mounts,
+ targetDirectory,
+ manifestFile,
+ true,
+ filesetContext.getWorkspaceName());
+ }
+ }
+
+ /** A parser for the MANIFEST files used by Filesets and runfiles. */
+ static void parseManifestFile(
+ FileSystem fs,
+ Map<PathFragment, Path> mounts,
+ PathFragment targetDirectory,
+ File manifestFile,
+ boolean isFilesetManifest,
+ String workspaceName)
+ throws IOException, ExecException {
+ 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(" ");
+
+ // The "target" field is always a relative path that is to be interpreted in this way:
+ // (1) If this is a fileset manifest and our workspace name is not empty, the first segment
+ // of each "target" path must be the workspace name, which is then stripped before further
+ // processing.
+ // (2) The "target" path is then appended to the "targetDirectory", which is a path relative
+ // to the execRoot. Together, this results in the full path in the execRoot in which place a
+ // symlink referring to "source" has to be created (see below).
+ 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]);
+ }
+
+ // The "source" field, if it exists, is always an absolute path and may point to any file in
+ // the filesystem (it is not limited to files in the workspace or execroot).
+ Path source;
+ switch (fields.length) {
+ case 1:
+ source = fs.getPath("/dev/null");
+ break;
+ case 2:
+ source = fs.getPath(fields[1]);
+ break;
+ default:
+ throw new IllegalStateException("'" + line + "' splits into more than 2 parts");
+ }
+
+ mounts.put(targetPath, source);
+ }
+ }
+
+ /** Mount all runfiles that the spawn needs as specified via its runfiles suppliers. */
+ protected void mountRunfilesFromSuppliers(Map<PathFragment, Path> mounts, Spawn spawn)
+ throws IOException {
+ Map<PathFragment, Map<PathFragment, Artifact>> rootsAndMappings =
+ spawn.getRunfilesSupplier().getMappings();
+ for (Map.Entry<PathFragment, Map<PathFragment, Artifact>> rootAndMappings :
+ rootsAndMappings.entrySet()) {
+ PathFragment root = rootAndMappings.getKey();
+ if (root.isAbsolute()) {
+ root = root.relativeTo(execRoot.asFragment());
+ }
+ for (Map.Entry<PathFragment, Artifact> mapping : rootAndMappings.getValue().entrySet()) {
+ Artifact sourceArtifact = mapping.getValue();
+ PathFragment source =
+ (sourceArtifact != null) ? sourceArtifact.getExecPath() : new PathFragment("/dev/null");
+
+ Preconditions.checkArgument(!mapping.getKey().isAbsolute());
+ PathFragment target = root.getRelative(mapping.getKey());
+ mounts.put(target, execRoot.getRelative(source));
+ }
+ }
+ }
+
+ /** Mount all inputs of the spawn. */
+ protected void mountInputs(
+ Map<PathFragment, Path> mounts, Spawn spawn, ActionExecutionContext actionExecutionContext) {
+ 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;
+ }
+ PathFragment mount = new PathFragment(input.getExecPathString());
+ mounts.put(mount, execRoot.getRelative(mount));
+ }
+ }
+
+ @Override
+ public boolean willExecuteRemotely(boolean remotable) {
+ return false;
+ }
+
+ @Override
+ public String toString() {
+ return "sandboxed";
+ }
+
+ @Override
+ public boolean shouldPropagateExecException() {
+ return verboseFailures && sandboxOptions.sandboxDebug;
+ }
+}
diff --git a/src/main/java/com/google/devtools/build/lib/sandbox/SymlinkedExecRoot.java b/src/main/java/com/google/devtools/build/lib/sandbox/SymlinkedExecRoot.java
new file mode 100644
index 0000000..de0f31f
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/sandbox/SymlinkedExecRoot.java
@@ -0,0 +1,115 @@
+// Copyright 2016 The Bazel Authors. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.devtools.build.lib.sandbox;
+
+import com.google.common.io.Files;
+import com.google.devtools.build.lib.util.Preconditions;
+import com.google.devtools.build.lib.vfs.FileSystemUtils;
+import com.google.devtools.build.lib.vfs.Path;
+import com.google.devtools.build.lib.vfs.PathFragment;
+import java.io.IOException;
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Set;
+
+/**
+ * Creates an execRoot for a Spawn that contains input files as symlinks to their original
+ * destination.
+ */
+final class SymlinkedExecRoot implements SandboxExecRoot {
+
+ private final Path sandboxExecRoot;
+
+ public SymlinkedExecRoot(Path sandboxExecRoot) {
+ this.sandboxExecRoot = sandboxExecRoot;
+ }
+
+ @Override
+ public void createFileSystem(
+ Map<PathFragment, Path> inputs, Collection<PathFragment> outputs, Set<Path> writableDirs)
+ throws IOException {
+ Set<Path> createdDirs = new HashSet<>();
+ FileSystemUtils.createDirectoryAndParentsWithCache(createdDirs, sandboxExecRoot);
+ createParentDirectoriesForInputs(createdDirs, inputs.keySet());
+ createSymlinksForInputs(inputs);
+ createWritableDirectories(createdDirs, writableDirs);
+ createDirectoriesForOutputs(createdDirs, outputs);
+ }
+
+ /**
+ * No input can be a child of another input, because otherwise we might try to create a symlink
+ * below another symlink we created earlier - which means we'd actually end up writing somewhere
+ * in the workspace.
+ *
+ * <p>If all inputs were regular files, this situation could naturally not happen - but
+ * unfortunately, we might get the occasional action that has directories in its inputs.
+ *
+ * <p>Creating all parent directories first ensures that we can safely create symlinks to
+ * directories, too, because we'll get an IOException with EEXIST if inputs happen to be nested
+ * once we start creating the symlinks for all inputs.
+ */
+ private void createParentDirectoriesForInputs(Set<Path> createdDirs, Set<PathFragment> inputs)
+ throws IOException {
+ for (PathFragment inputPath : inputs) {
+ Path dir = sandboxExecRoot.getRelative(inputPath).getParentDirectory();
+ Preconditions.checkArgument(dir.startsWith(sandboxExecRoot));
+ FileSystemUtils.createDirectoryAndParentsWithCache(createdDirs, dir);
+ }
+ }
+
+ private void createSymlinksForInputs(Map<PathFragment, Path> inputs) throws IOException {
+ // All input files are relative to the execroot.
+ for (Entry<PathFragment, Path> entry : inputs.entrySet()) {
+ Path key = sandboxExecRoot.getRelative(entry.getKey());
+ key.createSymbolicLink(entry.getValue());
+ }
+ }
+
+ private void createWritableDirectories(Set<Path> createdDirs, Set<Path> writableDirs)
+ throws IOException {
+ for (Path writablePath : writableDirs) {
+ if (writablePath.startsWith(sandboxExecRoot)) {
+ FileSystemUtils.createDirectoryAndParentsWithCache(createdDirs, writablePath);
+ }
+ }
+ }
+
+ /** Prepare the output directories in the sandbox. */
+ private void createDirectoriesForOutputs(Set<Path> createdDirs, Collection<PathFragment> outputs)
+ throws IOException {
+ for (PathFragment output : outputs) {
+ FileSystemUtils.createDirectoryAndParentsWithCache(
+ createdDirs, sandboxExecRoot.getRelative(output.getParentDirectory()));
+ }
+ }
+
+ /** Moves all {@code outputs} to {@code execRoot}. */
+ @Override
+ public void copyOutputs(Path execRoot, Collection<PathFragment> outputs) throws IOException {
+ Set<Path> createdDirs = new HashSet<>();
+ for (PathFragment output : outputs) {
+ Path source = sandboxExecRoot.getRelative(output);
+ if (source.isFile() || source.isSymbolicLink()) {
+ FileSystemUtils.createDirectoryAndParentsWithCache(
+ createdDirs, execRoot.getRelative(output.getParentDirectory()));
+
+ Path target = execRoot.getRelative(output);
+ Files.move(source.getPathFile(), target.getPathFile());
+ }
+ }
+ }
+}
diff --git a/src/main/java/com/google/devtools/build/lib/standalone/StandaloneSpawnStrategy.java b/src/main/java/com/google/devtools/build/lib/standalone/StandaloneSpawnStrategy.java
index 45cdafc..561942d 100644
--- a/src/main/java/com/google/devtools/build/lib/standalone/StandaloneSpawnStrategy.java
+++ b/src/main/java/com/google/devtools/build/lib/standalone/StandaloneSpawnStrategy.java
@@ -107,9 +107,12 @@
args.addAll(spawn.getArguments());
String cwd = executor.getExecRoot().getPathString();
- Command cmd = new Command(args.toArray(new String[]{}),
- locallyDeterminedEnv(spawn.getEnvironment()), new File(cwd),
- OS.getCurrent() == OS.WINDOWS && timeoutSeconds >= 0 ? timeoutSeconds * 1000 : -1);
+ Command cmd =
+ new Command(
+ args.toArray(new String[] {}),
+ locallyDeterminedEnv(execRoot, productName, spawn.getEnvironment()),
+ new File(cwd),
+ OS.getCurrent() == OS.WINDOWS && timeoutSeconds >= 0 ? timeoutSeconds * 1000 : -1);
FileOutErr outErr = actionExecutionContext.getFileOutErr();
try {
@@ -158,7 +161,8 @@
* @return the new environment, comprised of the old environment plus any new variables
* @throws UserExecException if any variables dependent on system state could not be resolved
*/
- public ImmutableMap<String, String> locallyDeterminedEnv(ImmutableMap<String, String> env)
+ public static ImmutableMap<String, String> locallyDeterminedEnv(
+ Path execRoot, String productName, ImmutableMap<String, String> env)
throws UserExecException {
// TODO(bazel-team): Remove apple-specific logic from this class.
ImmutableMap.Builder<String, String> newEnvBuilder = ImmutableMap.builder();
@@ -168,7 +172,9 @@
// should be explicitly set for build hermiticity.
String developerDir = "";
if (env.containsKey(AppleConfiguration.XCODE_VERSION_ENV_NAME)) {
- developerDir = getDeveloperDir(env.get(AppleConfiguration.XCODE_VERSION_ENV_NAME));
+ developerDir =
+ getDeveloperDir(
+ execRoot, productName, env.get(AppleConfiguration.XCODE_VERSION_ENV_NAME));
newEnvBuilder.put("DEVELOPER_DIR", developerDir);
}
if (env.containsKey(AppleConfiguration.APPLE_SDK_VERSION_ENV_NAME)) {
@@ -178,12 +184,15 @@
}
String iosSdkVersion = env.get(AppleConfiguration.APPLE_SDK_VERSION_ENV_NAME);
String appleSdkPlatform = env.get(AppleConfiguration.APPLE_SDK_PLATFORM_ENV_NAME);
- newEnvBuilder.put("SDKROOT", getSdkRootEnv(developerDir, iosSdkVersion, appleSdkPlatform));
+ newEnvBuilder.put(
+ "SDKROOT",
+ getSdkRootEnv(execRoot, productName, developerDir, iosSdkVersion, appleSdkPlatform));
}
return newEnvBuilder.build();
}
- private String getDeveloperDir(String xcodeVersion) throws UserExecException {
+ private static String getDeveloperDir(Path execRoot, String productName, String xcodeVersion)
+ throws UserExecException {
if (OS.getCurrent() != OS.DARWIN) {
throw new UserExecException(
"Cannot locate xcode developer directory on non-darwin operating system");
@@ -192,8 +201,13 @@
productName);
}
- private String getSdkRootEnv(String developerDir,
- String iosSdkVersion, String appleSdkPlatform) throws UserExecException {
+ private static String getSdkRootEnv(
+ Path execRoot,
+ String productName,
+ String developerDir,
+ String iosSdkVersion,
+ String appleSdkPlatform)
+ throws UserExecException {
if (OS.getCurrent() != OS.DARWIN) {
throw new UserExecException("Cannot locate iOS SDK on non-darwin operating system");
}