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