Changes to Fileset traversal code for internal purposes

PiperOrigin-RevId: 628051123
Change-Id: I35697db03e13fa866801ef81f36aaa8b2caab306
diff --git a/src/main/java/com/google/devtools/build/lib/actions/FilesetTraversalParams.java b/src/main/java/com/google/devtools/build/lib/actions/FilesetTraversalParams.java
index e214868..c14aa7f 100644
--- a/src/main/java/com/google/devtools/build/lib/actions/FilesetTraversalParams.java
+++ b/src/main/java/com/google/devtools/build/lib/actions/FilesetTraversalParams.java
@@ -182,6 +182,9 @@
     /** Returns whether Filesets treat outputs in a strict manner, assuming regular files. */
     public abstract boolean isStrictFilesetOutput();
 
+    /** Returns whether directories are permitted and can be traversed. */
+    public abstract boolean permitDirectories();
+
     @Memoized
     @Override
     public abstract int hashCode();
@@ -193,6 +196,7 @@
       fp.addBoolean(isFollowingSymlinks());
       fp.addBoolean(isGenerated());
       fp.addBoolean(isStrictFilesetOutput());
+      fp.addBoolean(permitDirectories());
       getPackageBoundaryMode().fingerprint(fp);
       return fp.digestAndReset();
     }
@@ -202,9 +206,15 @@
         boolean followingSymlinks,
         PackageBoundaryMode packageBoundaryMode,
         boolean isStrictFilesetOutput,
+        boolean permitSourceDirectories,
         boolean isGenerated) {
       return new AutoValue_FilesetTraversalParams_DirectTraversal(
-          root, isGenerated, followingSymlinks, packageBoundaryMode, isStrictFilesetOutput);
+          root,
+          isGenerated,
+          followingSymlinks,
+          packageBoundaryMode,
+          isStrictFilesetOutput,
+          permitSourceDirectories);
     }
   }
 
diff --git a/src/main/java/com/google/devtools/build/lib/actions/FilesetTraversalParamsFactory.java b/src/main/java/com/google/devtools/build/lib/actions/FilesetTraversalParamsFactory.java
index afe9d74..ea14899 100644
--- a/src/main/java/com/google/devtools/build/lib/actions/FilesetTraversalParamsFactory.java
+++ b/src/main/java/com/google/devtools/build/lib/actions/FilesetTraversalParamsFactory.java
@@ -63,9 +63,14 @@
    * @param pkgBoundaryMode what to do when the traversal hits a subdirectory that is also a
    * @param strictFilesetOutput whether Fileset assumes that output Artifacts are regular files.
    */
-  public static FilesetTraversalParams fileTraversal(Label ownerLabel, Artifact fileToTraverse,
-      PathFragment destPath, SymlinkBehavior symlinkBehaviorMode,
-      PackageBoundaryMode pkgBoundaryMode, boolean strictFilesetOutput) {
+  public static FilesetTraversalParams fileTraversal(
+      Label ownerLabel,
+      Artifact fileToTraverse,
+      PathFragment destPath,
+      SymlinkBehavior symlinkBehaviorMode,
+      PackageBoundaryMode pkgBoundaryMode,
+      boolean strictFilesetOutput,
+      boolean permitDirectories) {
     return DirectoryTraversalParams.getDirectoryTraversalParams(
         ownerLabel,
         DirectTraversalRoot.forFileOrDirectory(fileToTraverse),
@@ -74,6 +79,7 @@
         symlinkBehaviorMode,
         pkgBoundaryMode,
         strictFilesetOutput,
+        permitDirectories,
         !fileToTraverse.isSourceArtifact());
   }
 
@@ -138,6 +144,7 @@
         SymlinkBehavior symlinkBehaviorMode,
         PackageBoundaryMode pkgBoundaryMode,
         boolean strictFilesetOutput,
+        boolean permitDirectories,
         boolean isGenerated) {
       DirectTraversal traversal =
           DirectTraversal.getDirectTraversal(
@@ -145,6 +152,7 @@
               symlinkBehaviorMode == SymlinkBehavior.DEREFERENCE,
               pkgBoundaryMode,
               strictFilesetOutput,
+              permitDirectories,
               isGenerated);
       return new AutoValue_FilesetTraversalParamsFactory_DirectoryTraversalParams(
           ownerLabelForErrorMessages, destPath, getOrderedExcludes(excludes), traversal);
diff --git a/src/main/java/com/google/devtools/build/lib/skyframe/FilesetEntryFunction.java b/src/main/java/com/google/devtools/build/lib/skyframe/FilesetEntryFunction.java
index ecdbbd9..ebb565a 100644
--- a/src/main/java/com/google/devtools/build/lib/skyframe/FilesetEntryFunction.java
+++ b/src/main/java/com/google/devtools/build/lib/skyframe/FilesetEntryFunction.java
@@ -83,6 +83,18 @@
       return FilesetEntryValue.EMPTY;
     }
 
+    // Check if directory traversal is permitted
+    if (resolvedRoot.getType().isDirectory() && !params.getDirectTraversal().permitDirectories()) {
+      throw new FilesetEntryFunctionException(
+          new RecursiveFilesystemTraversalException(
+              String.format(
+                  "%s contains a directory artifact '%s' and is restricted by %s",
+                  params.getOwnerLabelForErrorMessages(),
+                  params.getDestPath(),
+                  "tools/allowlists/fileset_dir_in_files_allowlist"),
+              RecursiveFilesystemTraversalException.Type.FILE_OPERATION_FAILURE));
+    }
+
     // The "direct" traversal params are present, which is the case when the FilesetEntry
     // specifies a package's BUILD file, a directory or a list of files.
 
diff --git a/src/test/java/com/google/devtools/build/lib/skyframe/FilesetEntryFunctionTest.java b/src/test/java/com/google/devtools/build/lib/skyframe/FilesetEntryFunctionTest.java
index bf763af..5375433 100644
--- a/src/test/java/com/google/devtools/build/lib/skyframe/FilesetEntryFunctionTest.java
+++ b/src/test/java/com/google/devtools/build/lib/skyframe/FilesetEntryFunctionTest.java
@@ -234,12 +234,13 @@
     Artifact file = createSourceArtifact("foo/file.real");
     FilesetTraversalParams params =
         FilesetTraversalParamsFactory.fileTraversal(
-            /*ownerLabel=*/ label("//foo"),
-            /*fileToTraverse=*/ file,
+            /* ownerLabel= */ label("//foo"),
+            /* fileToTraverse= */ file,
             PathFragment.create("output-name"),
-            /*symlinkBehaviorMode=*/ FilesetTraversalParamsFactory.SymlinkBehavior.COPY,
-            /*pkgBoundaryMode=*/ DONT_CROSS,
-            /*strictFilesetOutput=*/ false);
+            /* symlinkBehaviorMode= */ FilesetTraversalParamsFactory.SymlinkBehavior.COPY,
+            /* pkgBoundaryMode= */ DONT_CROSS,
+            /* strictFilesetOutput= */ false,
+            /* permitDirectories= */ false);
     assertSymlinksCreatedInOrder(params, symlink("output-name", file));
   }
 
@@ -250,12 +251,13 @@
 
     FilesetTraversalParams params =
         FilesetTraversalParamsFactory.fileTraversal(
-            /*ownerLabel=*/ label("//foo"),
-            /*fileToTraverse=*/ symlink,
+            /* ownerLabel= */ label("//foo"),
+            /* fileToTraverse= */ symlink,
             PathFragment.create("output-name"),
-            /*symlinkBehaviorMode=*/ symlinks,
-            /*pkgBoundaryMode=*/ DONT_CROSS,
-            /*strictFilesetOutput=*/ false);
+            /* symlinkBehaviorMode= */ symlinks,
+            /* pkgBoundaryMode= */ DONT_CROSS,
+            /* strictFilesetOutput= */ false,
+            /* permitDirectories= */ false);
     switch (symlinks) {
       case COPY:
         assertSymlinksCreatedInOrder(params, symlink("output-name", "file.real"));
@@ -286,16 +288,41 @@
 
     FilesetTraversalParams params =
         FilesetTraversalParamsFactory.fileTraversal(
-            /*ownerLabel=*/ label("//foo"),
-            /*fileToTraverse=*/ dir,
+            /* ownerLabel= */ label("//foo"),
+            /* fileToTraverse= */ dir,
             PathFragment.create("output-name"),
-            /*symlinkBehaviorMode=*/ FilesetTraversalParamsFactory.SymlinkBehavior.COPY,
-            /*pkgBoundaryMode=*/ DONT_CROSS,
-            /*strictFilesetOutput*/ false);
+            /* symlinkBehaviorMode= */ FilesetTraversalParamsFactory.SymlinkBehavior.COPY,
+            /* pkgBoundaryMode= */ DONT_CROSS,
+            /* strictFilesetOutput= */ false,
+            /* permitDirectories= */ true);
     assertSymlinksCreatedInOrder(
         params, symlink("output-name/file.a", fileA), symlink("output-name/sub/file.b", fileB));
   }
 
+  @Test
+  public void testFileTraversalForDisallowedDirectoryThrows() throws Exception {
+    Artifact dir = getSourceArtifact("foo/dir_real");
+    createFile(childOf(dir, "file.a"), "hello");
+    createFile(childOf(dir, "sub/file.b"), "world");
+
+    FilesetTraversalParams params =
+        FilesetTraversalParamsFactory.fileTraversal(
+            /* ownerLabel= */ label("//foo"),
+            /* fileToTraverse= */ dir,
+            PathFragment.create("output-name"),
+            /* symlinkBehaviorMode= */ FilesetTraversalParamsFactory.SymlinkBehavior.COPY,
+            /* pkgBoundaryMode= */ DONT_CROSS,
+            /* strictFilesetOutput= */ false,
+            /* permitDirectories= */ false);
+
+    SkyKey key = FilesetEntryKey.key(params);
+    EvaluationResult<FilesetEntryValue> result = eval(key);
+    assertThat(result.hasError()).isTrue();
+    assertThat(result.getError(key).getException())
+        .hasMessageThat()
+        .contains("foo contains a directory");
+  }
+
   private void assertFileTraversalForDirectorySymlink(SymlinkBehavior symlinks) throws Exception {
     Artifact dir = getSourceArtifact("foo/dir_real");
     Artifact symlink = getSourceArtifact("foo/dir_sym");
@@ -305,12 +332,13 @@
 
     FilesetTraversalParams params =
         FilesetTraversalParamsFactory.fileTraversal(
-            /*ownerLabel=*/ label("//foo"),
-            /*fileToTraverse=*/ symlink,
+            /* ownerLabel= */ label("//foo"),
+            /* fileToTraverse= */ symlink,
             PathFragment.create("output-name"),
-            /*symlinkBehaviorMode=*/ symlinks,
-            /*pkgBoundaryMode=*/ DONT_CROSS,
-            /*strictFilesetOutput*/ false);
+            /* symlinkBehaviorMode= */ symlinks,
+            /* pkgBoundaryMode= */ DONT_CROSS,
+            /* strictFilesetOutput= */ false,
+            /* permitDirectories= */ true);
     switch (symlinks) {
       case COPY:
         assertSymlinksCreatedInOrder(params, symlink("output-name", "dir_real"));
@@ -343,12 +371,13 @@
 
     FilesetTraversalParams params =
         FilesetTraversalParamsFactory.fileTraversal(
-            /*ownerLabel=*/ label("//foo"),
-            /*fileToTraverse=*/ linkName,
+            /* ownerLabel= */ label("//foo"),
+            /* fileToTraverse= */ linkName,
             PathFragment.create("output-name"),
-            /*symlinkBehaviorMode=*/ FilesetTraversalParamsFactory.SymlinkBehavior.COPY,
-            /*pkgBoundaryMode=*/ DONT_CROSS,
-            /*strictFilesetOutput=*/ false);
+            /* symlinkBehaviorMode= */ FilesetTraversalParamsFactory.SymlinkBehavior.COPY,
+            /* pkgBoundaryMode= */ DONT_CROSS,
+            /* strictFilesetOutput= */ false,
+            /* permitDirectories= */ false);
     assertSymlinksCreatedInOrder(params); // expect empty results
   }
 
@@ -357,12 +386,13 @@
     Artifact path = getSourceArtifact("foo/non-existent");
     FilesetTraversalParams params =
         FilesetTraversalParamsFactory.fileTraversal(
-            /*ownerLabel=*/ label("//foo"),
-            /*fileToTraverse=*/ path,
+            /* ownerLabel= */ label("//foo"),
+            /* fileToTraverse= */ path,
             PathFragment.create("output-name"),
-            /*symlinkBehaviorMode=*/ FilesetTraversalParamsFactory.SymlinkBehavior.COPY,
-            /*pkgBoundaryMode=*/ DONT_CROSS,
-            /*strictFilesetOutput=*/ false);
+            /* symlinkBehaviorMode= */ FilesetTraversalParamsFactory.SymlinkBehavior.COPY,
+            /* pkgBoundaryMode= */ DONT_CROSS,
+            /* strictFilesetOutput= */ false,
+            /* permitDirectories= */ false);
     assertSymlinksCreatedInOrder(params); // expect empty results
   }
 
@@ -469,6 +499,7 @@
                     FilesetTraversalParamsFactory.SymlinkBehavior.DEREFERENCE))
             .put("pkgBoundaryMode", partOfFingerprint(CROSS, DONT_CROSS))
             .put("strictFilesetOutput", partOfFingerprint(true, false))
+            .put("permitDirectories", partOfFingerprint(true, false))
             .buildOrThrow()) {
       @Override
       FilesetTraversalParams create(Map<String, ?> kwArgs) throws Exception {
@@ -478,7 +509,8 @@
             PathFragment.create((String) kwArgs.get("destPath")),
             ((SymlinkBehavior) kwArgs.get("symlinkBehaviorMode")),
             (PackageBoundaryMode) kwArgs.get("pkgBoundaryMode"),
-            (Boolean) kwArgs.get("strictFilesetOutput"));
+            (Boolean) kwArgs.get("strictFilesetOutput"),
+            (Boolean) kwArgs.get("permitDirectories"));
       }
     }.doTest();
   }