aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorGravatar Xin Gao <xingao@google.com>2016-12-20 12:20:36 +0000
committerGravatar Klaus Aehlig <aehlig@google.com>2016-12-20 13:35:16 +0000
commit18e6b410a7f4e24b67740978d101cd665edea6cc (patch)
tree2d59b53517cfb9946bffb48172982fa252b4a6e9
parent4fb378c0fa08131394dc5e815f6fe5597d007a66 (diff)
Add customized path mounting in Bazel sandbox.
RELNOTES: New flag --sandbox_add_mount_pair to specify customized source:target path pairs to bind mount inside the sandbox. -- Change-Id: Ifbacfc0e16bbaedcf5b6d3937799710f2cfa3d58 Reviewed-on: https://cr.bazel.build/7150 PiperOrigin-RevId: 142542381 MOS_MIGRATED_REVID=142542381
-rw-r--r--src/main/java/com/google/devtools/build/lib/sandbox/LinuxSandboxRunner.java17
-rw-r--r--src/main/java/com/google/devtools/build/lib/sandbox/LinuxSandboxedStrategy.java74
-rw-r--r--src/main/java/com/google/devtools/build/lib/sandbox/SandboxOptions.java60
-rw-r--r--src/main/tools/linux-sandbox-options.cc62
-rw-r--r--src/main/tools/linux-sandbox-options.h6
-rw-r--r--src/main/tools/linux-sandbox-pid1.cc13
-rw-r--r--src/test/java/com/google/devtools/build/lib/sandbox/SandboxOptionsTest.java104
-rwxr-xr-xsrc/test/shell/bazel/bazel_sandboxing_test.sh91
-rwxr-xr-xsrc/test/shell/bazel/linux-sandbox_test.sh87
9 files changed, 465 insertions, 49 deletions
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 da50cda15a..97151e24ce 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
@@ -45,7 +45,8 @@ final class LinuxSandboxRunner extends SandboxRunner {
private final Set<Path> writableDirs;
private final Set<Path> inaccessiblePaths;
private final Set<Path> tmpfsPaths;
- private final Set<Path> bindMounts;
+ // a <target, source> mapping of paths to bind mount
+ private final Map<Path, Path> bindMounts;
private final boolean sandboxDebug;
LinuxSandboxRunner(
@@ -56,7 +57,7 @@ final class LinuxSandboxRunner extends SandboxRunner {
Set<Path> writableDirs,
Set<Path> inaccessiblePaths,
Set<Path> tmpfsPaths,
- Set<Path> bindMounts,
+ Map<Path, Path> bindMounts,
boolean verboseFailures,
boolean sandboxDebug) {
super(sandboxExecRoot, verboseFailures);
@@ -155,9 +156,15 @@ final class LinuxSandboxRunner extends SandboxRunner {
fileArgs.add(tmpfsPath.getPathString());
}
- for (Path bindMount : bindMounts) {
- fileArgs.add("-b");
- fileArgs.add(bindMount.getPathString());
+ for (ImmutableMap.Entry<Path, Path> bindMount : bindMounts.entrySet()) {
+ fileArgs.add("-M");
+ fileArgs.add(bindMount.getValue().getPathString());
+
+ // The file is mounted in a custom location inside the sandbox.
+ if (!bindMount.getKey().equals(bindMount.getValue())) {
+ fileArgs.add("-m");
+ fileArgs.add(bindMount.getKey().getPathString());
+ }
}
if (!allowNetwork) {
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 b4ed542ab1..dc4578ef61 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
@@ -14,7 +14,9 @@
package com.google.devtools.build.lib.sandbox;
+import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Maps;
import com.google.devtools.build.lib.actions.ActionExecutionContext;
import com.google.devtools.build.lib.actions.ExecException;
import com.google.devtools.build.lib.actions.ExecutionStrategy;
@@ -31,6 +33,7 @@ import com.google.devtools.build.lib.vfs.Path;
import com.google.devtools.build.lib.vfs.PathFragment;
import java.io.IOException;
import java.util.Set;
+import java.util.SortedMap;
import java.util.UUID;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;
@@ -149,7 +152,8 @@ public class LinuxSandboxedStrategy extends SandboxStrategy {
}
private SandboxRunner getSandboxRunner(
- Spawn spawn, Path sandboxPath, Path sandboxExecRoot, Path sandboxTempDir) {
+ Spawn spawn, Path sandboxPath, Path sandboxExecRoot, Path sandboxTempDir)
+ throws UserExecException {
if (fullySupported) {
return new LinuxSandboxRunner(
execRoot,
@@ -159,7 +163,7 @@ public class LinuxSandboxedStrategy extends SandboxStrategy {
getWritableDirs(sandboxExecRoot, spawn.getEnvironment()),
getInaccessiblePaths(),
getTmpfsPaths(),
- getBindMounts(blazeDirs),
+ getReadOnlyBindMounts(blazeDirs, sandboxExecRoot),
verboseFailures,
sandboxOptions.sandboxDebug);
} else {
@@ -175,15 +179,71 @@ public class LinuxSandboxedStrategy extends SandboxStrategy {
return tmpfsPaths.build();
}
- private ImmutableSet<Path> getBindMounts(BlazeDirectories blazeDirs) {
+ private SortedMap<Path, Path> getReadOnlyBindMounts(
+ BlazeDirectories blazeDirs, Path sandboxExecRoot) throws UserExecException {
Path tmpPath = blazeDirs.getFileSystem().getPath("/tmp");
- ImmutableSet.Builder<Path> bindMounts = ImmutableSet.builder();
+ final SortedMap<Path, Path> bindMounts = Maps.newTreeMap();
if (blazeDirs.getWorkspace().startsWith(tmpPath)) {
- bindMounts.add(blazeDirs.getWorkspace());
+ bindMounts.put(blazeDirs.getWorkspace(), blazeDirs.getWorkspace());
}
if (blazeDirs.getOutputBase().startsWith(tmpPath)) {
- bindMounts.add(blazeDirs.getOutputBase());
+ bindMounts.put(blazeDirs.getOutputBase(), blazeDirs.getOutputBase());
+ }
+ for (ImmutableMap.Entry<String, String> additionalMountPath :
+ sandboxOptions.sandboxAdditionalMounts) {
+ try {
+ final Path mountTarget = blazeDirs.getFileSystem().getPath(additionalMountPath.getValue());
+ // If source path is relative, treat it as a relative path inside the execution root
+ final Path mountSource = sandboxExecRoot.getRelative(additionalMountPath.getKey());
+ // If a target has more than one source path, the latter one will take effect.
+ bindMounts.put(mountTarget, mountSource);
+ } catch (IllegalArgumentException e) {
+ throw new UserExecException(
+ String.format("Error occurred when analyzing bind mount pairs. %s", e.getMessage()));
+ }
+ }
+ validateBindMounts(bindMounts);
+ return bindMounts;
+ }
+
+ /**
+ * This method does the following things: - If mount source does not exist on the host system,
+ * throw an error message - If mount target exists, check whether the source and target are of the
+ * same type - If mount target does not exist on the host system, throw an error message
+ *
+ * @param bindMounts the bind mounts map with target as key and source as value
+ * @throws UserExecException
+ */
+ private void validateBindMounts(SortedMap<Path, Path> bindMounts) throws UserExecException {
+ for (SortedMap.Entry<Path, Path> bindMount : bindMounts.entrySet()) {
+ final Path source = bindMount.getValue();
+ final Path target = bindMount.getKey();
+ // Mount source should exist in the file system
+ if (!source.exists()) {
+ throw new UserExecException(String.format("Mount source '%s' does not exist.", source));
+ }
+ // If target exists, but is not of the same type as the source, then we cannot mount it.
+ if (target.exists()) {
+ boolean areBothDirectories = source.isDirectory() && target.isDirectory();
+ boolean isSourceFile = source.isFile() || source.isSymbolicLink();
+ boolean isTargetFile = target.isFile() || target.isSymbolicLink();
+ boolean areBothFiles = isSourceFile && isTargetFile;
+ if (!(areBothDirectories || areBothFiles)) {
+ // Source and target are not of the same type; we cannot mount it.
+ throw new UserExecException(
+ String.format(
+ "Mount target '%s' is not of the same type as mount source '%s'.",
+ target, source));
+ }
+ } else {
+ // Mount target should exist in the file system
+ throw new UserExecException(
+ String.format(
+ "Mount target '%s' does not exist. Bazel only supports bind mounting on top of "
+ + "existing files/directories. Please create an empty file or directory at "
+ + "the mount target path according to the type of mount source.",
+ target));
+ }
}
- return bindMounts.build();
}
}
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 909e07cc76..b8ceb8094c 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
@@ -14,15 +14,59 @@
package com.google.devtools.build.lib.sandbox;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Maps;
+import com.google.devtools.common.options.Converter;
import com.google.devtools.common.options.Option;
import com.google.devtools.common.options.OptionsBase;
+import com.google.devtools.common.options.OptionsParsingException;
import java.util.List;
-/**
- * Options for sandboxed execution.
- */
+/** Options for sandboxed execution. */
public class SandboxOptions extends OptionsBase {
+ /**
+ * A converter for customized path mounting pair from the parameter list of a bazel command
+ * invocation. Pairs are expected to have the form 'source:target'.
+ */
+ public static final class MountPairConverter
+ implements Converter<ImmutableMap.Entry<String, String>> {
+
+ @Override
+ public ImmutableMap.Entry<String, String> convert(String input) throws OptionsParsingException {
+
+ List<String> paths = Lists.newArrayList();
+ for (String path : input.split("(?<!\\\\):")) { // Split on ':' but not on '\:'
+ if (path != null && !path.trim().isEmpty()) {
+ paths.add(path.replace("\\:", ":"));
+ } else {
+ throw new OptionsParsingException(
+ "Input "
+ + input
+ + " contains one or more empty paths. "
+ + "Input must be a single path to mount inside the sandbox or "
+ + "a mounting pair in the form of 'source:target'");
+ }
+ }
+
+ if (paths.size() < 1 || paths.size() > 2) {
+ throw new OptionsParsingException(
+ "Input must be a single path to mount inside the sandbox or "
+ + "a mounting pair in the form of 'source:target'");
+ }
+
+ return paths.size() == 1
+ ? Maps.immutableEntry(paths.get(0), paths.get(0))
+ : Maps.immutableEntry(paths.get(0), paths.get(1));
+ }
+
+ @Override
+ public String getTypeDescription() {
+ return "a single path or a 'source:target' pair";
+ }
+ }
+
@Option(
name = "ignore_unsupported_sandboxing",
defaultValue = "false",
@@ -59,4 +103,14 @@ public class SandboxOptions extends OptionsBase {
+ " (if supported by the sandboxing implementation, ignored otherwise)."
)
public List<String> sandboxTmpfsPath;
+
+ @Option(
+ name = "sandbox_add_mount_pair",
+ allowMultiple = true,
+ converter = MountPairConverter.class,
+ defaultValue = "",
+ category = "config",
+ help = "Add additional path pair to mount in sandbox."
+ )
+ public List<ImmutableMap.Entry<String, String>> sandboxAdditionalMounts;
}
diff --git a/src/main/tools/linux-sandbox-options.cc b/src/main/tools/linux-sandbox-options.cc
index 49e6eafeeb..8f8c15d5d9 100644
--- a/src/main/tools/linux-sandbox-options.cc
+++ b/src/main/tools/linux-sandbox-options.cc
@@ -69,7 +69,11 @@ static void Usage(char *program_name, const char *fmt, ...) {
" -i <file> make a file or directory inaccessible for the "
"sandboxed process\n"
" -e <dir> mount an empty tmpfs on a directory\n"
- " -b <dir> bind mount a file or directory inside the sandbox\n"
+ " -M/-m <source/target> directory to mount inside the sandbox\n"
+ " Multiple directories can be specified and each of them will be "
+ "mounted readonly.\n"
+ " The -M option specifies which directory to mount, the -m option "
+ "specifies where to\n"
" -N if set, a new network namespace will be created\n"
" -R if set, make the uid/gid be root, otherwise use nobody\n"
" -D if set, debug info will be printed\n"
@@ -106,15 +110,24 @@ static int CheckNamespacesSupported() {
return EXIT_SUCCESS;
}
+static void ValidateIsAbsolutePath(char *path, char *program_name, char flag) {
+ if (path[0] != '/') {
+ Usage(program_name, "The -%c option must be used with absolute paths only.",
+ flag);
+ }
+}
+
// Parses command line flags from an argv array and puts the results into an
// Options structure passed in as an argument.
static void ParseCommandLine(unique_ptr<vector<char *>> args) {
extern char *optarg;
extern int optind, optopt;
int c;
+ bool source_specified;
while ((c = getopt(args->size(), args->data(),
- ":CS:W:T:t:l:L:w:i:e:b:NRD")) != -1) {
+ ":CS:W:T:t:l:L:w:i:e:M:m:NRD")) != -1) {
+ if (c != 'M' && c != 'm') source_specified = false;
switch (c) {
case 'C':
// Shortcut for the "does this system support sandboxing" check.
@@ -122,22 +135,16 @@ static void ParseCommandLine(unique_ptr<vector<char *>> args) {
break;
case 'S':
if (opt.sandbox_root_dir == NULL) {
- if (optarg[0] != '/') {
- Usage(args->front(),
- "The -r option must be used with absolute paths only.");
- }
+ ValidateIsAbsolutePath(optarg, args->front(), static_cast<char>(c));
opt.sandbox_root_dir = strdup(optarg);
} else {
Usage(args->front(),
- "Multiple root directories (-r) specified, expected one.");
+ "Multiple root directories (-S) specified, expected one.");
}
break;
case 'W':
if (opt.working_dir == NULL) {
- if (optarg[0] != '/') {
- Usage(args->front(),
- "The -W option must be used with absolute paths only.");
- }
+ ValidateIsAbsolutePath(optarg, args->front(), static_cast<char>(c));
opt.working_dir = strdup(optarg);
} else {
Usage(args->front(),
@@ -173,32 +180,33 @@ static void ParseCommandLine(unique_ptr<vector<char *>> args) {
}
break;
case 'w':
- if (optarg[0] != '/') {
- Usage(args->front(),
- "The -w option must be used with absolute paths only.");
- }
+ ValidateIsAbsolutePath(optarg, args->front(), static_cast<char>(c));
opt.writable_files.push_back(strdup(optarg));
break;
case 'i':
- if (optarg[0] != '/') {
- Usage(args->front(),
- "The -i option must be used with absolute paths only.");
- }
+ ValidateIsAbsolutePath(optarg, args->front(), static_cast<char>(c));
opt.inaccessible_files.push_back(strdup(optarg));
break;
case 'e':
- if (optarg[0] != '/') {
- Usage(args->front(),
- "The -e option must be used with absolute paths only.");
- }
+ ValidateIsAbsolutePath(optarg, args->front(), static_cast<char>(c));
opt.tmpfs_dirs.push_back(strdup(optarg));
break;
- case 'b':
- if (optarg[0] != '/') {
+ case 'M':
+ ValidateIsAbsolutePath(optarg, args->front(), static_cast<char>(c));
+ // Add the current source path to both source and target lists
+ opt.bind_mount_sources.push_back(strdup(optarg));
+ opt.bind_mount_targets.push_back(strdup(optarg));
+ source_specified = true;
+ break;
+ case 'm':
+ ValidateIsAbsolutePath(optarg, args->front(), static_cast<char>(c));
+ if (!source_specified) {
Usage(args->front(),
- "The -b option must be used with absolute paths only.");
+ "The -m option must be strictly preceded by an -M option.");
}
- opt.bind_mounts.push_back(strdup(optarg));
+ opt.bind_mount_targets.pop_back();
+ opt.bind_mount_targets.push_back(strdup(optarg));
+ source_specified = false;
break;
case 'N':
opt.create_netns = true;
diff --git a/src/main/tools/linux-sandbox-options.h b/src/main/tools/linux-sandbox-options.h
index 5554f9e41f..5f33a0676f 100644
--- a/src/main/tools/linux-sandbox-options.h
+++ b/src/main/tools/linux-sandbox-options.h
@@ -40,8 +40,10 @@ struct Options {
std::vector<const char *> inaccessible_files;
// Directories where to mount an empty tmpfs (-e)
std::vector<const char *> tmpfs_dirs;
- // Files or directories to explicitly bind mount into the sandbox (-b)
- std::vector<const char *> bind_mounts;
+ // Source of files or directories to explicitly bind mount in the sandbox (-M)
+ std::vector<const char *> bind_mount_sources;
+ // Target of files or directories to explicitly bind mount in the sandbox (-m)
+ std::vector<const char *> bind_mount_targets;
// Create a new network namespace (-N)
bool create_netns;
// Pretend to be root inside the namespace (-R)
diff --git a/src/main/tools/linux-sandbox-pid1.cc b/src/main/tools/linux-sandbox-pid1.cc
index 4feedd9e02..5e0dff1a01 100644
--- a/src/main/tools/linux-sandbox-pid1.cc
+++ b/src/main/tools/linux-sandbox-pid1.cc
@@ -249,17 +249,20 @@ static void MountFilesystems() {
// Make sure that our working directory is a mount point. The easiest way to
// do this is by bind-mounting it upon itself.
PRINT_DEBUG("working dir: %s", opt.working_dir);
+ PRINT_DEBUG("sandbox root: %s", opt.sandbox_root_dir);
+
CreateTarget(opt.working_dir + 1, true);
if (mount(opt.working_dir, opt.working_dir + 1, NULL, MS_BIND, NULL) < 0) {
DIE("mount(%s, %s, NULL, MS_BIND, NULL)", opt.working_dir,
opt.working_dir + 1);
}
- for (const char *bind_mount : opt.bind_mounts) {
- PRINT_DEBUG("bind mount: %s", bind_mount);
- CreateTarget(bind_mount + 1, IsDirectory(bind_mount));
- if (mount(bind_mount, bind_mount + 1, NULL, MS_BIND, NULL) < 0) {
- DIE("mount(%s, %s, NULL, MS_BIND, NULL)", bind_mount, bind_mount + 1);
+ for (size_t i = 0; i < opt.bind_mount_sources.size(); i++) {
+ const char *source = opt.bind_mount_sources.at(i);
+ const char *target = opt.bind_mount_targets.at(i);
+ PRINT_DEBUG("bind mount: %s -> %s", source, target);
+ if (mount(source, target + 1, NULL, MS_BIND, NULL) < 0) {
+ DIE("mount(%s, %s, NULL, MS_BIND, NULL)", source, target + 1);
}
}
diff --git a/src/test/java/com/google/devtools/build/lib/sandbox/SandboxOptionsTest.java b/src/test/java/com/google/devtools/build/lib/sandbox/SandboxOptionsTest.java
new file mode 100644
index 0000000000..0441f782e1
--- /dev/null
+++ b/src/test/java/com/google/devtools/build/lib/sandbox/SandboxOptionsTest.java
@@ -0,0 +1,104 @@
+// 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 static org.junit.Assert.assertEquals;
+import static org.junit.Assert.fail;
+
+import com.google.common.collect.ImmutableMap;
+import com.google.devtools.common.options.OptionsParsingException;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/** Tests for {@code SandboxOptions}. */
+@RunWith(JUnit4.class)
+public final class SandboxOptionsTest extends SandboxTestCase {
+
+ private ImmutableMap.Entry<String, String> pathPair;
+
+ @Test
+ public void testParsingAdditionalMounts_SinglePathWithoutColonSucess() throws Exception {
+ String source = "/a/bc/def/gh";
+ String target = source;
+ String input = source;
+ pathPair = new SandboxOptions.MountPairConverter().convert(input);
+ assertMountPair(pathPair, source, target);
+ }
+
+ @Test
+ public void testParsingAdditionalMounts_SinglePathWithColonSucess() throws Exception {
+ String source = "/a/b:c/def/gh";
+ String target = source;
+ String input = "/a/b\\:c/def/gh";
+ pathPair = new SandboxOptions.MountPairConverter().convert(input);
+ assertMountPair(pathPair, source, target);
+ }
+
+ @Test
+ public void testParsingAdditionalMounts_PathPairWithoutColonSucess() throws Exception {
+ String source = "/a/bc/def/gh";
+ String target = "/1/2/3/4/5";
+ String input = source + ":" + target;
+ pathPair = new SandboxOptions.MountPairConverter().convert(input);
+ assertMountPair(pathPair, source, target);
+ }
+
+ @Test
+ public void testParsingAdditionalMounts_PathPairWithColonSucess() throws Exception {
+ String source = "/a:/bc:/d:ef/gh";
+ String target = ":/1/2/3/4/5";
+ String input = "/a\\:/bc\\:/d\\:ef/gh:\\:/1/2/3/4/5";
+ pathPair = new SandboxOptions.MountPairConverter().convert(input);
+ assertMountPair(pathPair, source, target);
+ }
+
+ @Test
+ public void testParsingAdditionalMounts_TooManyPaths() throws Exception {
+ String input = "a/bc/def/gh:/1/2/3:x/y/z";
+ try {
+ pathPair = new SandboxOptions.MountPairConverter().convert(input);
+ fail();
+ } catch (OptionsParsingException e) {
+ assertEquals(
+ e.getMessage(),
+ "Input must be a single path to mount inside the sandbox or "
+ + "a mounting pair in the form of 'source:target'");
+ }
+ }
+
+ @Test
+ public void testParsingAdditionalMounts_EmptyInput() throws Exception {
+ String input = "";
+ try {
+ pathPair = new SandboxOptions.MountPairConverter().convert(input);
+ fail();
+ } catch (OptionsParsingException e) {
+ assertEquals(
+ e.getMessage(),
+ "Input "
+ + input
+ + " contains one or more empty paths. "
+ + "Input must be a single path to mount inside the sandbox or "
+ + "a mounting pair in the form of 'source:target'");
+ }
+ }
+
+ private static void assertMountPair(
+ ImmutableMap.Entry<String, String> pathPair, String source, String target) {
+ assertEquals(pathPair.getKey(), source);
+ assertEquals(pathPair.getValue(), target);
+ }
+}
diff --git a/src/test/shell/bazel/bazel_sandboxing_test.sh b/src/test/shell/bazel/bazel_sandboxing_test.sh
index 5d2199bddc..df015bb5f4 100755
--- a/src/test/shell/bazel/bazel_sandboxing_test.sh
+++ b/src/test/shell/bazel/bazel_sandboxing_test.sh
@@ -445,6 +445,97 @@ EOF
expect_log "Executing genrule //:test failed: linux-sandbox failed: error executing command"
}
+function test_sandbox_mount_customized_path () {
+ # Create BUILD file
+ cat > BUILD <<'EOF'
+package(default_visibility = ["//visibility:public"])
+
+cc_binary(
+ name = "hello-world",
+ srcs = ["hello-world.cc"],
+)
+EOF
+
+ # Create cc file
+ cat > hello-world.cc << 'EOF'
+#include <iostream>
+int main(int argc, char** argv) {
+ std::cout << "Hello, world!" << std::endl;
+ return 0;
+}
+EOF
+
+ # Create WORKSPACE file
+ cat > WORKSPACE <<'EOF'
+local_repository(
+ name = 'x86_64_unknown_linux_gnu',
+ path = './downloaded_toolchain',
+)
+EOF
+
+ # Prepare the sandbox root
+ SANDBOX_ROOT="${TEST_TMPDIR}/sandbox.root"
+ mkdir -p ${SANDBOX_ROOT}
+
+ # Define the mount source and target.
+ source="${TEST_TMPDIR}/workspace/downloaded_toolchain/x86_64-unknown-linux-gnu/sysroot/lib64/ld-2.19.so"
+ target_root="${TEST_SRCDIR}/mount_targets"
+ target_folder="${target_root}/x86_64-unknown-linux-gnu/sysroot/lib64"
+ target="${target_folder}/ld-2.19.so"
+
+ # Download the toolchain package and unpack it.
+ wget -q https://asci-toolchain.appspot.com.storage.googleapis.com/toolchain-testing/mount_path_toolchain.tar.gz
+ mkdir downloaded_toolchain
+ tar -xf mount_path_toolchain.tar.gz -C ./downloaded_toolchain
+ chmod -R 0755 downloaded_toolchain
+
+ # Replace the target_root_placeholder with the actual target_root
+ sed -i "s|target_root_placeholder|$target_root|g" downloaded_toolchain/CROSSTOOL
+
+ # Prepare the bazel command flags
+ flags="--crosstool_top=@x86_64_unknown_linux_gnu//:toolchain --verbose_failures --spawn_strategy=sandboxed"
+ flags="${flags} --sandbox_add_mount_pair=${source}:${target}"
+
+ # Execute the bazel build command without creating the target. Should fail.
+ bazel clean --expunge &> $TEST_log
+ bazel build $flags //:hello-world &> $TEST_log && fail "Should fail"
+ expect_log "Bazel only supports bind mounting on top of existing files/directories."
+
+ # Create the mount target manually as Bazel does not create target paths
+ mkdir -p ${target_folder}
+ touch ${target}
+
+ # Execute bazel build command again. Should build.
+ bazel clean --expunge &> $TEST_log
+ bazel build $flags //:hello-world &> $TEST_log || fail "Should build"
+
+ # Remove the mount target folder as Bazel does not do the cleanup
+ rm -rf ${target_root}/x86_64-unknown-linux-gnu
+
+ # Assert that output binary exists
+ test -f bazel-bin/hello-world || fail "output not found"
+
+ # Use linux_sandbox binary to run bazel-bin/hello-world binary in the sandbox environment
+ # First, no path mounting. The execution should fail.
+ echo "Run the binary bazel-bin/hello-world without mounting the path"
+ $linux_sandbox -D -S ${SANDBOX_ROOT} -- bazel-bin/hello-world &> $TEST_log || code=$?
+ expect_log "child exited normally with exitcode 1"
+
+ # Second, with path mounting. The execution should succeed.
+ echo "Run the binary bazel-bin/hello-world with mounting the path"
+ # Create the mount target manually as sandbox binary does not create target paths
+ mkdir -p ${target_folder}
+ touch ${target}
+ $linux_sandbox -D -S ${SANDBOX_ROOT} \
+ -M ${source} \
+ -m ${target} \
+ -- bazel-bin/hello-world &> $TEST_log || code=$?
+ expect_log "Hello, world!"
+ expect_log "child exited normally with exitcode 0"
+ # Remove the mount target folder as sandbox binary does not do the cleanup
+ rm -rf ${target_root}/x86_64-unknown-linux-gnu
+}
+
# The test shouldn't fail if the environment doesn't support running it.
check_supported_platform || exit 0
check_sandbox_allowed || exit 0
diff --git a/src/test/shell/bazel/linux-sandbox_test.sh b/src/test/shell/bazel/linux-sandbox_test.sh
index 549f84fe88..c97f1901d4 100755
--- a/src/test/shell/bazel/linux-sandbox_test.sh
+++ b/src/test/shell/bazel/linux-sandbox_test.sh
@@ -28,12 +28,15 @@ readonly OUT_DIR="${TEST_TMPDIR}/out"
readonly OUT="${OUT_DIR}/outfile"
readonly ERR="${OUT_DIR}/errfile"
readonly SANDBOX_DIR="${OUT_DIR}/sandbox"
+readonly SANDBOX_ROOT="${TEST_TMPDIR}/sandbox.root"
+readonly MOUNT_TARGET_ROOT="${TEST_SRCDIR}/targets"
SANDBOX_DEFAULT_OPTS="-W $SANDBOX_DIR"
function set_up {
rm -rf $OUT_DIR
mkdir -p $SANDBOX_DIR
+ mkdir -p $SANDBOX_ROOT
}
function test_basic_functionality() {
@@ -111,6 +114,90 @@ function test_debug_logging() {
expect_log "child exited normally with exitcode 0"
}
+function test_mount_additional_paths_success() {
+ mkdir -p ${TEST_TMPDIR}/foo
+ mkdir -p ${TEST_TMPDIR}/bar
+ touch ${TEST_TMPDIR}/testfile
+ mkdir -p ${MOUNT_TARGET_ROOT}/foo
+ touch ${MOUNT_TARGET_ROOT}/sandboxed_testfile
+
+ touch /tmp/sandboxed_testfile
+ $linux_sandbox $SANDBOX_DEFAULT_OPTS -D -S ${SANDBOX_ROOT} \
+ -M ${TEST_TMPDIR}/foo -m ${MOUNT_TARGET_ROOT}/foo \
+ -M ${TEST_TMPDIR}/bar \
+ -M ${TEST_TMPDIR}/testfile -m ${MOUNT_TARGET_ROOT}/sandboxed_testfile \
+ -- /bin/true &> $TEST_log || code=$?
+ # mount a directory to a customized path inside the sandbox
+ expect_log "bind mount: ${TEST_TMPDIR}/foo -> ${MOUNT_TARGET_ROOT}/foo\$"
+ # mount a directory to the same path inside the sanxbox
+ expect_log "bind mount: ${TEST_TMPDIR}/bar -> ${TEST_TMPDIR}/bar\$"
+ # mount a file to a customized path inside the sandbox
+ expect_log "bind mount: ${TEST_TMPDIR}/testfile -> ${MOUNT_TARGET_ROOT}/sandboxed_testfile\$"
+ expect_log "child exited normally with exitcode 0"
+ rm -rf ${MOUNT_TARGET_ROOT}/foo
+ rm -rf ${MOUNT_TARGET_ROOT}/sandboxed_testfile
+}
+
+function test_mount_additional_paths_relative_path() {
+ touch ${TEST_TMPDIR}/testfile
+ $linux_sandbox $SANDBOX_DEFAULT_OPTS -D -S ${SANDBOX_ROOT} \
+ -M ${TEST_TMPDIR}/testfile -m tmp/sandboxed_testfile \
+ -- /bin/true &> $TEST_log || code=$?
+ # mount a directory to a customized path inside the sandbox
+ expect_log "The -m option must be used with absolute paths only.\$"
+}
+
+function test_mount_additional_paths_leading_m() {
+ mkdir -p ${TEST_TMPDIR}/foo
+ touch ${TEST_TMPDIR}/testfile
+ $linux_sandbox $SANDBOX_DEFAULT_OPTS -D -S ${SANDBOX_ROOT} \
+ -m /tmp/foo \
+ -M ${TEST_TMPDIR}/testfile -m /tmp/sandboxed_testfile \
+ -- /bin/true &> $TEST_log || code=$?
+ # mount a directory to a customized path inside the sandbox
+ expect_log "The -m option must be strictly preceded by an -M option.\$"
+}
+
+function test_mount_additional_paths_m_not_preceeded_by_M() {
+ mkdir -p ${TEST_TMPDIR}/foo
+ mkdir -p ${TEST_TMPDIR}/bar
+ touch ${TEST_TMPDIR}/testfile
+ $linux_sandbox $SANDBOX_DEFAULT_OPTS -D -S ${SANDBOX_ROOT} \
+ -M ${TEST_TMPDIR}/testfile -m /tmp/sandboxed_testfile \
+ -m /tmp/foo \
+ -M ${TEST_TMPDIR}/bar \
+ -- /bin/true &> $TEST_log || code=$?
+ # mount a directory to a customized path inside the sandbox
+ expect_log "The -m option must be strictly preceded by an -M option.\$"
+}
+
+function test_mount_additional_paths_other_flag_between_M_m_pair() {
+ mkdir -p ${TEST_TMPDIR}/bar
+ touch ${TEST_TMPDIR}/testfile
+ $linux_sandbox $SANDBOX_DEFAULT_OPTS -S ${SANDBOX_ROOT} \
+ -M ${TEST_TMPDIR}/testfile -D -m /tmp/sandboxed_testfile \
+ -M ${TEST_TMPDIR}/bar \
+ -- /bin/true &> $TEST_log || code=$?
+ # mount a directory to a customized path inside the sandbox
+ expect_log "The -m option must be strictly preceded by an -M option.\$"
+}
+
+function test_mount_additional_paths_multiple_sources_mount_to_one_target() {
+ mkdir -p ${TEST_TMPDIR}/foo
+ mkdir -p ${TEST_TMPDIR}/bar
+ mkdir -p ${MOUNT_TARGET_ROOT}/foo
+ $linux_sandbox $SANDBOX_DEFAULT_OPTS -D -S ${SANDBOX_ROOT} \
+ -M ${TEST_TMPDIR}/foo -m ${MOUNT_TARGET_ROOT}/foo \
+ -M ${TEST_TMPDIR}/bar -m ${MOUNT_TARGET_ROOT}/foo \
+ -- /bin/true &> $TEST_log || code=$?
+ # mount a directory to a customized path inside the sandbox
+ expect_log "bind mount: ${TEST_TMPDIR}/foo -> ${MOUNT_TARGET_ROOT}/foo\$"
+ # mount a new source directory to the same target, which will overwrite the previous source path
+ expect_log "bind mount: ${TEST_TMPDIR}/bar -> ${MOUNT_TARGET_ROOT}/foo\$"
+ expect_log "child exited normally with exitcode 0"
+ rm -rf ${MOUNT_TARGET_ROOT}/foo
+}
+
function test_redirect_output() {
$linux_sandbox $SANDBOX_DEFAULT_OPTS -l $OUT -L $ERR -- /bin/bash -c "echo out; echo err >&2" &> $TEST_log || code=$?
assert_equals "out" "$(cat $OUT)"