Ensure the workspace name appears in paths within sandboxfs sandboxes.

Some tools walk path hiearchies looking for the workspace name and
misbehave if that component is not present.  All sandboxed spawn runners
have provisions to deal with this and explicitly add the workspace name
to the execroots they generate... but the sandboxfs spawn runner was not
doing so.  Fix it now.

RELNOTES: None.
PiperOrigin-RevId: 299850812
diff --git a/src/main/java/com/google/devtools/build/lib/sandbox/DarwinSandboxedSpawnRunner.java b/src/main/java/com/google/devtools/build/lib/sandbox/DarwinSandboxedSpawnRunner.java
index 329df1b..b7cd5bc 100644
--- a/src/main/java/com/google/devtools/build/lib/sandbox/DarwinSandboxedSpawnRunner.java
+++ b/src/main/java/com/google/devtools/build/lib/sandbox/DarwinSandboxedSpawnRunner.java
@@ -218,7 +218,8 @@
 
     // b/64689608: The execroot of the sandboxed process must end with the workspace name, just like
     // the normal execroot does.
-    Path sandboxExecRoot = sandboxPath.getRelative("execroot").getRelative(execRoot.getBaseName());
+    String workspaceName = execRoot.getBaseName();
+    Path sandboxExecRoot = sandboxPath.getRelative("execroot").getRelative(workspaceName);
     sandboxExecRoot.getParentDirectory().createDirectory();
     sandboxExecRoot.createDirectory();
 
@@ -271,6 +272,7 @@
       return new SandboxfsSandboxedSpawn(
           sandboxfsProcess,
           sandboxPath,
+          workspaceName,
           commandLine,
           environment,
           inputs,
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 50ee1ed..4c5a2c1 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
@@ -149,7 +149,8 @@
 
     // b/64689608: The execroot of the sandboxed process must end with the workspace name, just like
     // the normal execroot does.
-    Path sandboxExecRoot = sandboxPath.getRelative("execroot").getRelative(execRoot.getBaseName());
+    String workspaceName = execRoot.getBaseName();
+    Path sandboxExecRoot = sandboxPath.getRelative("execroot").getRelative(workspaceName);
     sandboxExecRoot.getParentDirectory().createDirectory();
     sandboxExecRoot.createDirectory();
 
@@ -193,6 +194,7 @@
       return new SandboxfsSandboxedSpawn(
           sandboxfsProcess,
           sandboxPath,
+          workspaceName,
           commandLineBuilder.build(),
           environment,
           SandboxHelpers.processInputFiles(
diff --git a/src/main/java/com/google/devtools/build/lib/sandbox/ProcessWrapperSandboxedSpawnRunner.java b/src/main/java/com/google/devtools/build/lib/sandbox/ProcessWrapperSandboxedSpawnRunner.java
index 6a743b1..f5ce8ea 100644
--- a/src/main/java/com/google/devtools/build/lib/sandbox/ProcessWrapperSandboxedSpawnRunner.java
+++ b/src/main/java/com/google/devtools/build/lib/sandbox/ProcessWrapperSandboxedSpawnRunner.java
@@ -87,7 +87,8 @@
 
     // b/64689608: The execroot of the sandboxed process must end with the workspace name, just like
     // the normal execroot does.
-    Path sandboxExecRoot = sandboxPath.getRelative("execroot").getRelative(execRoot.getBaseName());
+    String workspaceName = execRoot.getBaseName();
+    Path sandboxExecRoot = sandboxPath.getRelative("execroot").getRelative(workspaceName);
     sandboxExecRoot.getParentDirectory().createDirectory();
     sandboxExecRoot.createDirectory();
 
@@ -119,6 +120,7 @@
       return new SandboxfsSandboxedSpawn(
           sandboxfsProcess,
           sandboxPath,
+          workspaceName,
           commandLineBuilder.build(),
           environment,
           inputs,
diff --git a/src/main/java/com/google/devtools/build/lib/sandbox/SandboxfsSandboxedSpawn.java b/src/main/java/com/google/devtools/build/lib/sandbox/SandboxfsSandboxedSpawn.java
index 54ed909..a69ed2f 100644
--- a/src/main/java/com/google/devtools/build/lib/sandbox/SandboxfsSandboxedSpawn.java
+++ b/src/main/java/com/google/devtools/build/lib/sandbox/SandboxfsSandboxedSpawn.java
@@ -44,9 +44,6 @@
   /** Sequence number to assign a unique subtree to each action within the mount point. */
   private static final AtomicInteger lastId = new AtomicInteger();
 
-  /** Single instance of a path fragment representing a root directory. */
-  private static final PathFragment rootFragment = PathFragment.create("/");
-
   /** The sandboxfs instance to use for this spawn. */
   private final SandboxfsProcess process;
 
@@ -92,6 +89,9 @@
    */
   private final String sandboxName;
 
+  /** Path to the execroot within the sandbox. */
+  private final PathFragment rootFragment;
+
   /** Flag to track whether the sandbox needs to be unmapped. */
   private boolean sandboxIsMapped;
 
@@ -114,6 +114,7 @@
   SandboxfsSandboxedSpawn(
       SandboxfsProcess process,
       Path sandboxPath,
+      String workspaceName,
       List<String> arguments,
       Map<String, String> environment,
       SandboxInputs inputs,
@@ -146,8 +147,14 @@
     int id = lastId.getAndIncrement();
     this.sandboxName = "" + id;
     this.sandboxIsMapped = false;
-    this.execRoot = process.getMountPoint().getRelative(this.sandboxName);
     this.statisticsPath = statisticsPath;
+
+    // b/64689608: The execroot of the sandboxed process must end with the workspace name, just
+    // like the normal execroot does. Some tools walk their path hierarchy looking for this
+    // component and misbehave if they don't find it.
+    this.execRoot =
+        process.getMountPoint().getRelative(this.sandboxName).getRelative(workspaceName);
+    this.rootFragment = PathFragment.create("/" + workspaceName);
   }
 
   @Override
@@ -183,7 +190,7 @@
       sandboxScratchDir.getRelative(dir).createDirectoryAndParents();
     }
 
-    createSandbox(process, sandboxName, sandboxScratchDir, inputs, mapSymlinkTargets);
+    createSandbox(process, sandboxName, rootFragment, sandboxScratchDir, inputs, mapSymlinkTargets);
     sandboxIsMapped = true;
   }
 
@@ -284,6 +291,7 @@
    *
    * @param process the sandboxfs instance on which to create the sandbox
    * @param sandboxName the name of the sandbox to pass to sandboxfs
+   * @param rootFragment path within the sandbox to the execroot to create
    * @param scratchDir writable used as the target for all writable mappings
    * @param inputs collection of paths to expose within the sandbox as read-only mappings, given as
    *     a map of mapped path to target path. The target path may be null, in which case an empty
@@ -295,6 +303,7 @@
   private static void createSandbox(
       SandboxfsProcess process,
       String sandboxName,
+      PathFragment rootFragment,
       Path scratchDir,
       SandboxInputs inputs,
       boolean sandboxfsMapSymlinkTargets)
diff --git a/src/test/java/com/google/devtools/build/lib/sandbox/SandboxfsSandboxedSpawnTest.java b/src/test/java/com/google/devtools/build/lib/sandbox/SandboxfsSandboxedSpawnTest.java
index e906cbe..5ad8852 100644
--- a/src/test/java/com/google/devtools/build/lib/sandbox/SandboxfsSandboxedSpawnTest.java
+++ b/src/test/java/com/google/devtools/build/lib/sandbox/SandboxfsSandboxedSpawnTest.java
@@ -68,6 +68,7 @@
         new SandboxfsSandboxedSpawn(
             sandboxfs,
             outerDir,
+            "workspace",
             ImmutableList.of("/bin/true"),
             ImmutableMap.of(),
             new SandboxInputs(
@@ -90,6 +91,30 @@
   }
 
   @Test
+  public void testExecRootContainsWorkspaceName() throws Exception {
+    Path helloTxt = workspaceDir.getRelative("hello.txt");
+    FileSystemUtils.createEmptyFile(helloTxt);
+
+    SandboxedSpawn spawn =
+        new SandboxfsSandboxedSpawn(
+            sandboxfs,
+            outerDir,
+            "some-workspace-name",
+            ImmutableList.of("/bin/true"),
+            ImmutableMap.of(),
+            new SandboxInputs(ImmutableMap.of(), ImmutableMap.of()),
+            SandboxOutputs.create(ImmutableSet.of(), ImmutableSet.of()),
+            ImmutableSet.of(),
+            /* mapSymlinkTargets= */ false,
+            new SynchronousTreeDeleter(),
+            /* statisticsPath= */ null);
+    spawn.createFileSystem();
+    Path execRoot = spawn.getSandboxExecRoot();
+
+    assertThat(execRoot.getPathString()).contains("/some-workspace-name");
+  }
+
+  @Test
   public void testDelete() throws Exception {
     Path helloTxt = workspaceDir.getRelative("hello.txt");
     FileSystemUtils.createEmptyFile(helloTxt);
@@ -98,6 +123,7 @@
         new SandboxfsSandboxedSpawn(
             sandboxfs,
             outerDir,
+            "workspace",
             ImmutableList.of("/bin/true"),
             ImmutableMap.of(),
             new SandboxInputs(
@@ -132,6 +158,7 @@
         new SandboxfsSandboxedSpawn(
             sandboxfs,
             outerDir,
+            "workspace",
             ImmutableList.of("/bin/true"),
             ImmutableMap.of(),
             new SandboxInputs(ImmutableMap.of(), ImmutableMap.of()),
@@ -187,6 +214,7 @@
         new SandboxfsSandboxedSpawn(
             sandboxfs,
             outerDir,
+            "workspace",
             ImmutableList.of("/bin/true"),
             ImmutableMap.of(),
             new SandboxInputs(