aboutsummaryrefslogtreecommitdiffhomepage
path: root/src/main/java/com/google/devtools/build/lib/sandbox/DarwinSandboxRunner.java
diff options
context:
space:
mode:
authorGravatar Yue Gan <yueg@google.com>2016-08-08 12:39:02 +0000
committerGravatar Yue Gan <yueg@google.com>2016-08-08 12:43:30 +0000
commit4257ff9d3d24ceb10d1d88166c0a8a8d6a30d2b5 (patch)
treea434c8b5960a2f8d2acbd710ede8692aa43a4d48 /src/main/java/com/google/devtools/build/lib/sandbox/DarwinSandboxRunner.java
parent3f23411bd9501d0488b26ffa2041cf69a7c1ad79 (diff)
Sandbox 2.0 for Mac OS X.
-- Change-Id: Idf232f3dce3a3221d9a35c89dcef13437b0c25ba Reviewed-on: https://bazel-review.googlesource.com/#/c/3905/ MOS_MIGRATED_REVID=129620348
Diffstat (limited to 'src/main/java/com/google/devtools/build/lib/sandbox/DarwinSandboxRunner.java')
-rw-r--r--src/main/java/com/google/devtools/build/lib/sandbox/DarwinSandboxRunner.java293
1 files changed, 293 insertions, 0 deletions
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
new file mode 100644
index 0000000000..b871d08b49
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/sandbox/DarwinSandboxRunner.java
@@ -0,0 +1,293 @@
+// Copyright 2014 The Bazel Authors. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.devtools.build.lib.sandbox;
+
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.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.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;
+
+/**
+ * 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 {
+
+ 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 Path sandboxConfigPath;
+ private final ImmutableMap<PathFragment, Path> linkPaths;
+
+ public DarwinSandboxRunner(
+ Path execRoot,
+ Path sandboxPath,
+ Path sandboxExecRoot,
+ Path sandboxConfigPath,
+ ImmutableMap<PathFragment, Path> linkPaths,
+ ImmutableSet<PathFragment> createDirs,
+ boolean verboseFailures,
+ boolean sandboxDebug) {
+ this.execRoot = execRoot;
+ this.sandboxPath = sandboxPath;
+ 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;
+ }
+
+ static boolean isSupported() {
+ List<String> args = new ArrayList<>();
+ args.add("sandbox-exec");
+ args.add("-p");
+ args.add("(version 1) (allow default)");
+ args.add("/usr/bin/true");
+
+ ImmutableMap<String, String> env = ImmutableMap.of();
+ File cwd = new File("/usr/bin");
+
+ Command cmd = new Command(args.toArray(new String[0]), env, cwd);
+ try {
+ cmd.execute(
+ /* stdin */ new byte[] {},
+ Command.NO_OBSERVER,
+ ByteStreams.nullOutputStream(),
+ ByteStreams.nullOutputStream(),
+ /* killSubprocessOnInterrupt */ true);
+ } catch (CommandException e) {
+ return false;
+ }
+
+ return true;
+ }
+
+ /**
+ * 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)
+ throws IOException {
+ FileSystem fs = sandboxPath.getFileSystem();
+ PrintWriter errWriter = new PrintWriter(outErr.getErrorStream());
+ 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("-f");
+ commandLineArgs.add(sandboxConfigPath.getPathString());
+ commandLineArgs.addAll(spawnArguments);
+
+ return commandLineArgs;
+ }
+
+ /**
+ * 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();
+ }
+
+ 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);
+ }
+
+ // symlink
+ Path symlinkNewPath = sandboxExecRoot.getRelative(entry.getKey());
+ if (sandboxDebug) {
+ errWriter.printf("symlink: %s -> %s\n", hardlinkNewPath, symlinkNewPath);
+ }
+ FileSystemUtils.createDirectoryAndParents(symlinkNewPath.getParentDirectory());
+ symlinkNewPath.createSymbolicLink(hardlinkNewPath);
+ }
+ }
+
+ // 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);
+ }
+ 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);
+ }
+ }
+ }
+}