sandbox: Easier debugging of sandbox failures, when using both --verbose_failures and --sandbox_debug.

RELNOTES:
- When using both --verbose_failures and --sandbox_debug, Bazel prints instructions how to spawn a debugging shell inside the sandbox.
- When namespace-sandbox is run with the -D (debug) flag and inside a terminal, it spawns a shell inside the sandbox to aid in debugging when the sandboxed command fails.

--
MOS_MIGRATED_REVID=114953983
diff --git a/src/main/java/com/google/devtools/build/lib/sandbox/NamespaceSandboxRunner.java b/src/main/java/com/google/devtools/build/lib/sandbox/NamespaceSandboxRunner.java
index ed6f714..c6069bd 100644
--- a/src/main/java/com/google/devtools/build/lib/sandbox/NamespaceSandboxRunner.java
+++ b/src/main/java/com/google/devtools/build/lib/sandbox/NamespaceSandboxRunner.java
@@ -47,6 +47,11 @@
 public class NamespaceSandboxRunner {
   private static final String NAMESPACE_SANDBOX =
       "namespace-sandbox" + OsUtils.executableExtension();
+  private static final String SANDBOX_TIP =
+      "\n\nSandboxed execution failed, which may be legitimate (e.g. a compiler error), "
+          + "or due to missing dependencies. To enter the sandbox environment for easier debugging,"
+          + " run the following command in brackets. On command failure, "
+          + "a bash shell running inside the sandbox will then automatically be spawned\n\n";
   private final Path execRoot;
   private final Path sandboxPath;
   private final Path sandboxExecRoot;
@@ -198,8 +203,9 @@
       }
       String message =
           CommandFailureUtils.describeCommandFailure(
-              verboseFailures, spawnArguments, env, cwd.getPath());
-      throw new UserExecException(message, e, timedOut);
+              verboseFailures, commandLineArgs, env, cwd.getPath());
+      String finalMsg = (sandboxDebug && verboseFailures) ? SANDBOX_TIP + message : message;
+      throw new UserExecException(finalMsg, e, timedOut);
     } finally {
       copyOutputs(outputs);
     }
@@ -230,7 +236,7 @@
     if (sandboxPath.exists()) {
       FileSystemUtils.deleteTree(sandboxPath);
     }
-    if (argumentsFilePath.exists()) {
+    if (!sandboxDebug && argumentsFilePath.exists()) {
       argumentsFilePath.delete();
     }
   }
diff --git a/src/main/tools/namespace-sandbox.c b/src/main/tools/namespace-sandbox.c
index ce81d57..04f1c9c 100644
--- a/src/main/tools/namespace-sandbox.c
+++ b/src/main/tools/namespace-sandbox.c
@@ -16,6 +16,7 @@
 
 #include <errno.h>
 #include <fcntl.h>
+#include <ftw.h>
 #include <libgen.h>
 #include <limits.h>
 #include <pwd.h>
@@ -476,7 +477,31 @@
   CHECK_CALL(symlink("/proc/self/fd", "dev/fd"));
 }
 
+static int rmrf(const char *fpath, const struct stat *sb, int typeflag,
+                struct FTW *ftwbuf) {
+  if (typeflag == FTW_DP) {
+    return rmdir(fpath);
+  } else {
+    return unlink(fpath);
+  }
+}
+
 static void SetupDirectories(struct Options *opt) {
+  // If in sandbox_debug mode and debugging, create the sandbox root dir first
+  if (global_debug && isatty(fileno(stdin))) {
+    // Enter sandbox_debug mode a second time, delete old sandbox
+    struct stat sb;
+    int err = stat(opt->sandbox_root, &sb);
+    if (err == 0) {
+      CHECK_CALL(nftw(opt->sandbox_root, *rmrf, sysconf(_SC_OPEN_MAX),
+                      FTW_DEPTH | FTW_PHYS));
+    } else if (errno != ENOENT) {
+      CHECK_CALL(err);
+    }
+
+    CHECK_CALL(mkdir(opt->sandbox_root, 0755));
+  }
+
   // Mount the sandbox and go there.
   CHECK_CALL(mount(opt->sandbox_root, opt->sandbox_root, NULL,
                    MS_BIND | MS_NOSUID, NULL));
@@ -653,11 +678,11 @@
 
 // Run the command specified by the argv array and kill it after timeout
 // seconds.
-static void SpawnCommand(char *const *argv, double timeout_secs) {
+static void SpawnCommand(char *const *argv, double timeout_secs,
+                         bool isFallback) {
   for (int i = 0; argv[i] != NULL; i++) {
     PRINT_DEBUG("arg: %s\n", argv[i]);
   }
-
   CHECK_CALL(global_child_pid = fork());
   if (global_child_pid == 0) {
     // In child.
@@ -691,6 +716,13 @@
       UnHandle(global_signal);
       raise(global_signal);
     } else if (WIFEXITED(status)) {
+      if (global_debug && !isFallback && isatty(fileno(stdin)) &&
+          WEXITSTATUS(status) > 0) {
+        char **cmdList = calloc(2, sizeof(char *));
+        cmdList[0] = "/bin/bash";
+        cmdList[1] = NULL;
+        SpawnCommand(cmdList, 0, true);
+      }
       exit(WEXITSTATUS(status));
     } else {
       int sig = WTERMSIG(status);
@@ -749,7 +781,7 @@
   }
   ChangeRoot(&opt);
 
-  SpawnCommand(opt.args, opt.timeout_secs);
+  SpawnCommand(opt.args, opt.timeout_secs, false);
 
   free(opt.create_dirs);
   free(opt.mount_sources);