[sandbox] Add experimental flags for windows sandbox
Closes #8785.
PiperOrigin-RevId: 256653464
diff --git a/src/main/java/com/google/devtools/build/lib/sandbox/RealSandboxfsProcess.java b/src/main/java/com/google/devtools/build/lib/sandbox/RealSandboxfsProcess.java
index 199568a..3bf345a 100644
--- a/src/main/java/com/google/devtools/build/lib/sandbox/RealSandboxfsProcess.java
+++ b/src/main/java/com/google/devtools/build/lib/sandbox/RealSandboxfsProcess.java
@@ -100,7 +100,6 @@
*
* @param binary path to the sandboxfs binary that will later be used in the {@link #mount} call.
* @return true if the binary looks good, false otherwise
- * @throws IOException if there is a problem trying to start the subprocess
*/
static boolean isAvailable(PathFragment binary) {
Subprocess process;
@@ -112,7 +111,7 @@
.redirectErrorStream(true)
.start();
} catch (IOException e) {
- log.warning("sandboxfs binary at " + binary + " seems to be missing; got error " + e);
+ log.warning("sandboxfs binary at " + binary + " seems to be missing; got error: " + e);
return false;
}
@@ -121,14 +120,14 @@
ByteStreams.copy(process.getInputStream(), outErrBytes);
} catch (IOException e) {
try {
- outErrBytes.write(("Failed to read stdout: " + e).getBytes());
+ outErrBytes.write(("Failed to read stdout: " + e).getBytes("UTF-8"));
} catch (IOException e2) {
// Should not really have happened. There is nothing we can do.
}
}
String outErr = outErrBytes.toString().replaceFirst("\n$", "");
- int exitCode = waitForProcess(process);
+ int exitCode = SandboxHelpers.waitForProcess(process);
if (exitCode == 0) {
// TODO(jmmv): Validate the version number and ensure we support it. Would be nice to reuse
// the DottedVersion logic from the Apple rules.
@@ -226,31 +225,6 @@
}
/**
- * Waits for a process to terminate.
- *
- * @param process the process to wait for
- * @return the exit code of the terminated process
- */
- private static int waitForProcess(Subprocess process) {
- boolean interrupted = false;
- try {
- while (true) {
- try {
- process.waitFor();
- break;
- } catch (InterruptedException ie) {
- interrupted = true;
- }
- }
- } finally {
- if (interrupted) {
- Thread.currentThread().interrupt();
- }
- }
- return process.exitValue();
- }
-
- /**
* Destroys a process and waits for it to exit.
*
* @param process the process to destroy
@@ -259,7 +233,7 @@
// of Uninterruptibles.callUninterruptibly that takes a lambda instead of a callable.
private static void destroyProcess(Subprocess process) {
process.destroy();
- waitForProcess(process);
+ SandboxHelpers.waitForProcess(process);
}
@Override
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 48caa71..672be69 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
@@ -27,6 +27,7 @@
import com.google.devtools.build.lib.actions.cache.VirtualActionInput.EmptyActionInput;
import com.google.devtools.build.lib.analysis.test.TestConfiguration;
import com.google.devtools.build.lib.exec.SpawnRunner.SpawnExecutionContext;
+import com.google.devtools.build.lib.shell.Subprocess;
import com.google.devtools.build.lib.vfs.Path;
import com.google.devtools.build.lib.vfs.PathFragment;
import com.google.devtools.common.options.OptionsParsingResult;
@@ -160,4 +161,29 @@
.testArguments
.contains("--wrapper_script_flag=--debug");
}
+
+ /**
+ * Waits for a process to terminate.
+ *
+ * @param process the process to wait for
+ * @return the exit code of the terminated process
+ */
+ static int waitForProcess(Subprocess process) {
+ boolean interrupted = false;
+ try {
+ while (true) {
+ try {
+ process.waitFor();
+ break;
+ } catch (InterruptedException ie) {
+ interrupted = true;
+ }
+ }
+ } finally {
+ if (interrupted) {
+ Thread.currentThread().interrupt();
+ }
+ }
+ return process.exitValue();
+ }
}
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 e8ce079..578b34e 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
@@ -146,12 +146,11 @@
/**
* Returns true if sandboxfs should be used for this build.
*
- * <p>If the user set the use of sandboxfs as optional, this only returns true if the configured
- * sandboxfs binary is present and valid. If the user requested the use of sandboxfs as mandatory,
- * this throws an error if the binary is not valid.
+ * <p>Returns true if requested in ["auto", "yes"] and binary is valid. Throws an error if state
+ * is "yes" and binary is not valid.
*
* @param requested whether sandboxfs use was requested or not
- * @param binary path of the sandboxfs binary to use
+ * @param binary path of the sandboxfs binary to use, can be absolute or relative path
* @return true if sandboxfs can and should be used; false otherwise
* @throws IOException if there are problems trying to determine the status of sandboxfs
*/
@@ -175,6 +174,38 @@
throw new IllegalStateException("Not reachable");
}
+ /**
+ * Returns true if windows-sandbox should be used for this build.
+ *
+ * <p>Returns true if requested in ["auto", "yes"] and binary is valid. Throws an error if state
+ * is "yes" and binary is not valid.
+ *
+ * @param requested whether windows-sandbox use was requested or not
+ * @param binary path of the windows-sandbox binary to use, can be absolute or relative path
+ * @return true if windows-sandbox can and should be used; false otherwise
+ * @throws IOException if there are problems trying to determine the status of windows-sandbox
+ */
+ private boolean shouldUseWindowsSandbox(TriState requested, PathFragment binary)
+ throws IOException {
+ switch (requested) {
+ case AUTO:
+ return WindowsSandboxUtil.isAvailable(binary);
+
+ case NO:
+ return false;
+
+ case YES:
+ if (!WindowsSandboxUtil.isAvailable(binary)) {
+ throw new IOException(
+ "windows-sandbox explicitly requested but \""
+ + binary
+ + "\" could not be found or is not valid");
+ }
+ return true;
+ }
+ throw new IllegalStateException("Not reachable");
+ }
+
private void setup(CommandEnvironment cmdEnv, ExecutorBuilder builder)
throws IOException {
SandboxOptions options = checkNotNull(env.getOptions().getOptions(SandboxOptions.class));
@@ -240,6 +271,12 @@
}
}
+ PathFragment windowsSandboxPath = PathFragment.create(options.windowsSandboxPath);
+ boolean useWindowsSandbox;
+ try (SilentCloseable c = Profiler.instance().profile("shouldUseWindowsSandbox")) {
+ useWindowsSandbox = shouldUseWindowsSandbox(options.useWindowsSandbox, windowsSandboxPath);
+ }
+
Duration timeoutKillDelay =
cmdEnv.getOptions().getOptions(LocalExecutionOptions.class).getLocalSigkillGraceSeconds();
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 ed745d2..5e5c85a 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
@@ -212,6 +212,29 @@
+ "this on their own and should be removed once all such rules are fixed.")
public boolean sandboxfsMapSymlinkTargets;
+ @Option(
+ name = "experimental_use_windows_sandbox",
+ converter = TriStateConverter.class,
+ defaultValue = "false",
+ documentationCategory = OptionDocumentationCategory.EXECUTION_STRATEGY,
+ effectTags = {OptionEffectTag.UNKNOWN},
+ help =
+ "Use Windows sandbox to run actions. "
+ + "If \"yes\", the binary provided by --experimental_windows_sandbox_path must be "
+ + "valid and correspond to a supported version of sandboxfs. If \"auto\", the binary "
+ + "may be missing or not compatible.")
+ public TriState useWindowsSandbox;
+
+ @Option(
+ name = "experimental_windows_sandbox_path",
+ defaultValue = "BazelSandbox.exe",
+ documentationCategory = OptionDocumentationCategory.EXECUTION_STRATEGY,
+ effectTags = {OptionEffectTag.UNKNOWN},
+ help =
+ "Path to the Windows sandbox binary to use when --experimental_use_windows_sandbox is"
+ + " true. If a bare name, use the first binary of that name found in the PATH.")
+ public String windowsSandboxPath;
+
public ImmutableSet<Path> getInaccessiblePaths(FileSystem fs) {
List<Path> inaccessiblePaths = new ArrayList<>();
for (String path : sandboxBlockPath) {
diff --git a/src/main/java/com/google/devtools/build/lib/sandbox/WindowsSandboxUtil.java b/src/main/java/com/google/devtools/build/lib/sandbox/WindowsSandboxUtil.java
new file mode 100644
index 0000000..4b8af84
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/sandbox/WindowsSandboxUtil.java
@@ -0,0 +1,199 @@
+// Copyright 2019 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.base.Preconditions;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.io.ByteStreams;
+import com.google.devtools.build.lib.shell.Subprocess;
+import com.google.devtools.build.lib.shell.SubprocessBuilder;
+import com.google.devtools.build.lib.shell.SubprocessBuilder.StreamAction;
+import com.google.devtools.build.lib.vfs.Path;
+import com.google.devtools.build.lib.vfs.PathFragment;
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.time.Duration;
+import java.util.List;
+import java.util.Set;
+import java.util.logging.Logger;
+
+/** Utility functions for the {@code windows-sandbox}. */
+public final class WindowsSandboxUtil {
+ private static final Logger log = Logger.getLogger(WindowsSandboxUtil.class.getName());
+
+ /**
+ * Checks if the given Windows sandbox binary is available and is valid.
+ *
+ * @param binary path to the Windows sandbox binary
+ * @return true if the binary looks good, false otherwise
+ */
+ public static boolean isAvailable(PathFragment binary) {
+ Subprocess process;
+ try {
+ process =
+ new SubprocessBuilder()
+ .setArgv(binary.getPathString(), "-h")
+ .setStdout(StreamAction.STREAM)
+ .redirectErrorStream(true)
+ .setWorkingDirectory(new File("."))
+ .start();
+ } catch (IOException e) {
+ log.warning("Windows sandbox binary at " + binary + " seems to be missing; got error: " + e);
+ return false;
+ }
+
+ ByteArrayOutputStream outErrBytes = new ByteArrayOutputStream();
+ try {
+ ByteStreams.copy(process.getInputStream(), outErrBytes);
+ } catch (IOException e) {
+ try {
+ outErrBytes.write(("Failed to read stdout: " + e).getBytes(StandardCharsets.UTF_8));
+ } catch (IOException e2) {
+ // Should not really have happened. There is nothing we can do.
+ }
+ }
+ String outErr = outErrBytes.toString().replaceFirst("\n$", "");
+
+ int exitCode = SandboxHelpers.waitForProcess(process);
+ if (exitCode == 0) {
+ // TODO(rongjiecomputer): Validate the version number and ensure we support it. Would be nice
+ // to reuse
+ // the DottedVersion logic from the Apple rules.
+ return true;
+ } else {
+ log.warning(
+ "Windows sandbox binary at "
+ + binary
+ + " returned non-zero exit code "
+ + exitCode
+ + " and output "
+ + outErr);
+ return false;
+ }
+ }
+
+ /** Returns a new command line builder for the {@code windows-sandbox} tool. */
+ public static CommandLineBuilder commandLineBuilder(
+ PathFragment windowsSandboxPath, List<String> commandArguments) {
+ return new CommandLineBuilder(windowsSandboxPath, commandArguments);
+ }
+
+ /**
+ * A builder class for constructing the full command line to run a command using the {@code
+ * windows-sandbox} tool.
+ */
+ public static class CommandLineBuilder {
+ private final PathFragment windowsSandboxPath;
+ private Path workingDirectory;
+ private Duration timeout;
+ private Duration killDelay;
+ private Path stdoutPath;
+ private Path stderrPath;
+ private Set<Path> writableFilesAndDirectories = ImmutableSet.of();
+ private boolean useDebugMode = false;
+ private List<String> commandArguments = ImmutableList.of();
+
+ private CommandLineBuilder(PathFragment windowsSandboxPath, List<String> commandArguments) {
+ this.windowsSandboxPath = windowsSandboxPath;
+ this.commandArguments = commandArguments;
+ }
+
+ /** Sets the working directory to use, if any. */
+ public CommandLineBuilder setWorkingDirectory(Path workingDirectory) {
+ this.workingDirectory = workingDirectory;
+ return this;
+ }
+
+ /** Sets the timeout for the command run using the {@code windows-sandbox} tool. */
+ public CommandLineBuilder setTimeout(Duration timeout) {
+ this.timeout = timeout;
+ return this;
+ }
+
+ /**
+ * Sets the kill delay for commands run using the {@code windows-sandbox} tool that exceed their
+ * timeout.
+ */
+ public CommandLineBuilder setKillDelay(Duration killDelay) {
+ this.killDelay = killDelay;
+ return this;
+ }
+
+ /** Sets the path to use for redirecting stdout, if any. */
+ public CommandLineBuilder setStdoutPath(Path stdoutPath) {
+ this.stdoutPath = stdoutPath;
+ return this;
+ }
+
+ /** Sets the path to use for redirecting stderr, if any. */
+ public CommandLineBuilder setStderrPath(Path stderrPath) {
+ this.stderrPath = stderrPath;
+ return this;
+ }
+
+ /** Sets the files or directories to make writable for the sandboxed process, if any. */
+ public CommandLineBuilder setWritableFilesAndDirectories(
+ Set<Path> writableFilesAndDirectories) {
+ this.writableFilesAndDirectories = writableFilesAndDirectories;
+ return this;
+ }
+
+ /** Sets whether to enable debug mode (e.g. to print debugging messages). */
+ public CommandLineBuilder setUseDebugMode(boolean useDebugMode) {
+ this.useDebugMode = useDebugMode;
+ return this;
+ }
+
+ /**
+ * Builds the command line to invoke a specific command using the {@code windows-sandbox} tool.
+ */
+ public ImmutableList<String> build() {
+ Preconditions.checkNotNull(this.windowsSandboxPath, "windowsSandboxPath is required");
+ Preconditions.checkState(!this.commandArguments.isEmpty(), "commandArguments are required");
+
+ ImmutableList.Builder<String> commandLineBuilder = ImmutableList.builder();
+
+ commandLineBuilder.add(windowsSandboxPath.getPathString());
+ if (workingDirectory != null) {
+ commandLineBuilder.add("-W", workingDirectory.getPathString());
+ }
+ if (timeout != null) {
+ commandLineBuilder.add("-T", Long.toString(timeout.getSeconds()));
+ }
+ if (killDelay != null) {
+ commandLineBuilder.add("-t", Long.toString(killDelay.getSeconds()));
+ }
+ if (stdoutPath != null) {
+ commandLineBuilder.add("-l", stdoutPath.getPathString());
+ }
+ if (stderrPath != null) {
+ commandLineBuilder.add("-L", stderrPath.getPathString());
+ }
+ for (Path writablePath : writableFilesAndDirectories) {
+ commandLineBuilder.add("-w", writablePath.getPathString());
+ }
+ if (useDebugMode) {
+ commandLineBuilder.add("-D");
+ }
+ commandLineBuilder.add("--");
+ commandLineBuilder.addAll(commandArguments);
+
+ return commandLineBuilder.build();
+ }
+ }
+}