diff --git a/src/main/java/com/google/devtools/build/lib/sandbox/AbstractSandboxSpawnRunner.java b/src/main/java/com/google/devtools/build/lib/sandbox/AbstractSandboxSpawnRunner.java index 90a8c2cfd3d4b1..b703d4b80bf3ec 100644 --- a/src/main/java/com/google/devtools/build/lib/sandbox/AbstractSandboxSpawnRunner.java +++ b/src/main/java/com/google/devtools/build/lib/sandbox/AbstractSandboxSpawnRunner.java @@ -181,7 +181,10 @@ private SpawnResult runSpawn( /** Override this method if you need to run a post condition after the action has executed */ public void verifyPostCondition( Spawn originalSpawn, SandboxedSpawn sandbox, SpawnExecutionContext context) - throws IOException {} + throws IOException { + SandboxHelpers.validateOutputs( + SandboxHelpers.getOutputs(originalSpawn), sandbox.getSandboxExecRoot()); + } private String makeFailureMessage(Spawn originalSpawn, SandboxedSpawn sandbox) { if (sandboxOptions.sandboxDebug) { diff --git a/src/main/java/com/google/devtools/build/lib/sandbox/LinuxSandboxedSpawnRunner.java b/src/main/java/com/google/devtools/build/lib/sandbox/LinuxSandboxedSpawnRunner.java index 415aacc0cba249..4ae1320fa361a6 100644 --- a/src/main/java/com/google/devtools/build/lib/sandbox/LinuxSandboxedSpawnRunner.java +++ b/src/main/java/com/google/devtools/build/lib/sandbox/LinuxSandboxedSpawnRunner.java @@ -460,6 +460,7 @@ private ImmutableMap prepareAndGetBindMounts( public void verifyPostCondition( Spawn originalSpawn, SandboxedSpawn sandbox, SpawnExecutionContext context) throws IOException { + super.verifyPostCondition(originalSpawn, sandbox, context); if (getSandboxOptions().useHermetic) { checkForConcurrentModifications(context); } 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 efb5b339f945c7..04b7c8e8adc287 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 @@ -235,6 +235,23 @@ public static void moveOutputs(SandboxOutputs outputs, Path sourceRoot, Path tar } } + /** + * Validates that all symbolic links in the outputs are not dangling. + * + * @param outputs outputs to validate as relative paths to a root + * @param sourceRoot root directory to validate from + * @throws IOException if a dangling symbolic link is found + */ + public static void validateOutputs(SandboxOutputs outputs, Path sourceRoot) throws IOException { + for (Entry output : + Iterables.concat(outputs.files().entrySet(), outputs.dirs().entrySet())) { + Path source = sourceRoot.getRelative(output.getValue()); + if (source.isSymbolicLink() && !source.exists()) { + throw new IOException("declared output '" + output.getKey() + "' is a dangling symbolic link"); + } + } + } + private static void copyFile(Path source, Path target) throws IOException { try (InputStream in = source.getInputStream(); OutputStream out = target.getOutputStream()) { diff --git a/src/test/shell/bazel/bazel_sandboxing_test.sh b/src/test/shell/bazel/bazel_sandboxing_test.sh index 0159b2a78a5943..e576a9e2c084dc 100755 --- a/src/test/shell/bazel/bazel_sandboxing_test.sh +++ b/src/test/shell/bazel/bazel_sandboxing_test.sh @@ -874,6 +874,33 @@ EOF assert_equals world "$(cat bazel-bin/pkg/some_file2.json)" } +function test_dangling_symlink_sandboxed_deterministic_failure() { + # Regression test for https://github.com/bazelbuild/bazel/issues/3759 + cat << 'EOF' > BUILD +genrule( + name = "make-target", + outs = ["target"], + cmd = "touch $@", +) +genrule( + name = "make-link", + outs = ["link"], + cmd = "ln -s target $@", +) +EOF + + # 1. Build :link first. It should FAIL because :target doesn't exist in the sandbox. + bazel build :link &> $TEST_log && fail "Expected :link to fail when :target is missing" || true + expect_log "is a dangling symbolic link" + + # 2. Build :target. + bazel build :target &> $TEST_log || fail "Expected :target to build successfully" + + # 3. Build :link again. This should still FAIL because :target is not a declared input. + bazel build :link &> $TEST_log && fail "Expected :link to fail even after :target is built" || true + expect_log "is a dangling symbolic link" +} + # The test shouldn't fail if the environment doesn't support running it. check_sandbox_allowed || exit 0