[libc-commits] [libc] [libc] Implement scandir and its unit tests (PR #223198)

Vadim Kotov via libc-commits libc-commits at lists.llvm.org
Fri Sep 25 16:48:26 PDT 2026


https://github.com/vadimkotov updated https://github.com/llvm/llvm-project/pull/223198

>From 701e5dc56a256097e3d0874510a70b29ddd1a8f9 Mon Sep 17 00:00:00 2001
From: Vadim Kotov <kotov at google.com>
Date: Thu, 3 Sep 2026 15:17:45 -0700
Subject: [PATCH 01/30] Create boilerplate code for scandir and its unittest.

---
 libc/config/linux/x86_64/entrypoints.txt |  1 +
 libc/include/dirent.yaml                 |  9 +++++++
 libc/src/dirent/CMakeLists.txt           | 12 +++++++++
 libc/src/dirent/scandir.cpp              | 34 ++++++++++++++++++++++++
 libc/src/dirent/scandir.h                | 28 +++++++++++++++++++
 libc/test/src/dirent/CMakeLists.txt      | 11 ++++++++
 libc/test/src/dirent/scandir_test.cpp    | 21 +++++++++++++++
 7 files changed, 116 insertions(+)
 create mode 100644 libc/src/dirent/scandir.cpp
 create mode 100644 libc/src/dirent/scandir.h
 create mode 100644 libc/test/src/dirent/scandir_test.cpp

diff --git a/libc/config/linux/x86_64/entrypoints.txt b/libc/config/linux/x86_64/entrypoints.txt
index 165484987f16d7..bfbc59b0e0a48d 100644
--- a/libc/config/linux/x86_64/entrypoints.txt
+++ b/libc/config/linux/x86_64/entrypoints.txt
@@ -1330,6 +1330,7 @@ if(LLVM_LIBC_FULL_BUILD)
     libc.src.dirent.dirfd
     libc.src.dirent.opendir
     libc.src.dirent.readdir
+    libc.src.dirent.scandir
     libc.src.dirent.fdopendir
 
     # pthread.h entrypoints
diff --git a/libc/include/dirent.yaml b/libc/include/dirent.yaml
index e752b564d8d447..8dc939fa1158a8 100644
--- a/libc/include/dirent.yaml
+++ b/libc/include/dirent.yaml
@@ -64,3 +64,12 @@ functions:
     return_type: struct dirent *
     arguments:
       - type: DIR *
+  - name: scandir
+    standards:
+      - posix
+    return_type: int
+    arguments:
+      - type: const char *
+      - type: struct dirent ***
+      - type: int (*)(const struct dirent *)
+      - type: int (*)(const struct dirent **, const struct dirent **)
diff --git a/libc/src/dirent/CMakeLists.txt b/libc/src/dirent/CMakeLists.txt
index d0ec22e1cc75b8..8b8c09b661b32e 100644
--- a/libc/src/dirent/CMakeLists.txt
+++ b/libc/src/dirent/CMakeLists.txt
@@ -49,6 +49,18 @@ add_entrypoint_object(
     libc.src.errno.errno
 )
 
+add_entrypoint_object(
+  scandir
+  SRCS
+    scandir.cpp
+  HDRS
+    scandir.h
+  DEPENDS
+    libc.hdr.types.struct_dirent
+    libc.src.__support.common
+    libc.src.__support.macros.config
+)
+
 add_entrypoint_object(
   fdopendir
   SRCS
diff --git a/libc/src/dirent/scandir.cpp b/libc/src/dirent/scandir.cpp
new file mode 100644
index 00000000000000..18b7779a3e260c
--- /dev/null
+++ b/libc/src/dirent/scandir.cpp
@@ -0,0 +1,34 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+///
+/// \file
+/// Implementation of getpriority.
+///
+//===----------------------------------------------------------------------===//
+
+#include "src/dirent/scandir.h"
+
+#include "hdr/types/struct_dirent.h"
+#include "src/__support/common.h"
+#include "src/__support/macros/config.h"
+
+namespace LIBC_NAMESPACE_DECL {
+
+LLVM_LIBC_FUNCTION(int, scandir,
+                   (const char *dir, struct dirent ***namelist,
+                    int (*sel)(const struct dirent *),
+                    int (*compar)(const struct dirent **,
+                                  const struct dirent **))) {
+  (void)dir;
+  (void)namelist;
+  (void)sel;
+  (void)compar;
+  return -1;
+}
+
+} // namespace LIBC_NAMESPACE_DECL
diff --git a/libc/src/dirent/scandir.h b/libc/src/dirent/scandir.h
new file mode 100644
index 00000000000000..03ffe076c781fc
--- /dev/null
+++ b/libc/src/dirent/scandir.h
@@ -0,0 +1,28 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+///
+/// \file
+/// Implementation header of scandir
+///
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_LIBC_SRC_DIRENT_SCANDIR_H
+#define LLVM_LIBC_SRC_DIRENT_SCANDIR_H
+
+#include "hdr/types/struct_dirent.h"
+#include "src/__support/macros/config.h"
+
+namespace LIBC_NAMESPACE_DECL {
+
+int scandir(const char *dir, struct dirent ***namelist,
+            int (*sel)(const struct dirent *),
+            int (*compar)(const struct dirent **, const struct dirent **));
+
+} // namespace LIBC_NAMESPACE_DECL
+
+#endif // LLVM_LIBC_SRC_DIRENT_SCANDIR_H
diff --git a/libc/test/src/dirent/CMakeLists.txt b/libc/test/src/dirent/CMakeLists.txt
index 1d7a3caf790ddd..fd56b23d2c4ed2 100644
--- a/libc/test/src/dirent/CMakeLists.txt
+++ b/libc/test/src/dirent/CMakeLists.txt
@@ -37,3 +37,14 @@ add_libc_test(
     libc.hdr.types.DIR
     libc.hdr.types.struct_dirent
 )
+
+add_libc_test(
+  scandir_test
+  SUITE
+    libc_dirent_unittests
+  SRCS
+    scandir_test.cpp
+  DEPENDS
+    libc.hdr.types.struct_dirent
+    libc.src.dirent.scandir
+)
diff --git a/libc/test/src/dirent/scandir_test.cpp b/libc/test/src/dirent/scandir_test.cpp
new file mode 100644
index 00000000000000..34e5e83c598a01
--- /dev/null
+++ b/libc/test/src/dirent/scandir_test.cpp
@@ -0,0 +1,21 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+///
+/// \file
+/// Unittests for scandir.
+///
+//===----------------------------------------------------------------------===//
+
+#include "hdr/types/struct_dirent.h"
+#include "src/dirent/scandir.h"
+#include "test/UnitTest/Test.h"
+
+TEST(LlvmLibcScandirTest, DummyTest) {
+  struct dirent **namelist;
+  ASSERT_NE(LIBC_NAMESPACE::scandir(".", &namelist, NULL, NULL), -1);
+}

>From cf270df9d31c31ad470d942a9650f37982ca3ada Mon Sep 17 00:00:00 2001
From: Vadim Kotov <kotov at google.com>
Date: Tue, 8 Sep 2026 10:15:51 -0700
Subject: [PATCH 02/30] Fix a stray change after rebase

---
 libc/config/linux/x86_64/entrypoints.txt | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/libc/config/linux/x86_64/entrypoints.txt b/libc/config/linux/x86_64/entrypoints.txt
index bfbc59b0e0a48d..2ff99649c2b141 100644
--- a/libc/config/linux/x86_64/entrypoints.txt
+++ b/libc/config/linux/x86_64/entrypoints.txt
@@ -630,7 +630,7 @@ set(TARGET_LIBM_ENTRYPOINTS
     libc.src.fenv.fesetexceptflag
     libc.src.fenv.fesetround
     libc.src.fenv.fetestexcept
-    libc.src.fenv.fetestexceptflag
+    libc.src.fenv.fegestexceptflag
     libc.src.fenv.feupdateenv
 
     # math.h entrypoints

>From 25e59714e8c4794d376c321b5e1b916bfc06cb67 Mon Sep 17 00:00:00 2001
From: Vadim Kotov <kotov at google.com>
Date: Tue, 8 Sep 2026 11:19:50 -0700
Subject: [PATCH 03/30] Mid-work commit due to a post-renase issue

---
 libc/src/dirent/CMakeLists.txt        |  1 +
 libc/src/dirent/scandir.cpp           | 12 ++++++++++--
 libc/test/src/dirent/CMakeLists.txt   |  2 ++
 libc/test/src/dirent/scandir_test.cpp |  9 +++++++--
 4 files changed, 20 insertions(+), 4 deletions(-)

diff --git a/libc/src/dirent/CMakeLists.txt b/libc/src/dirent/CMakeLists.txt
index 8b8c09b661b32e..4ad2a8b286a039 100644
--- a/libc/src/dirent/CMakeLists.txt
+++ b/libc/src/dirent/CMakeLists.txt
@@ -59,6 +59,7 @@ add_entrypoint_object(
     libc.hdr.types.struct_dirent
     libc.src.__support.common
     libc.src.__support.macros.config
+    libc.src.dirent.opendir
 )
 
 add_entrypoint_object(
diff --git a/libc/src/dirent/scandir.cpp b/libc/src/dirent/scandir.cpp
index 18b7779a3e260c..45351d7aad0340 100644
--- a/libc/src/dirent/scandir.cpp
+++ b/libc/src/dirent/scandir.cpp
@@ -16,6 +16,7 @@
 #include "hdr/types/struct_dirent.h"
 #include "src/__support/common.h"
 #include "src/__support/macros/config.h"
+#include "src/dirent/opendir.h"
 
 namespace LIBC_NAMESPACE_DECL {
 
@@ -24,11 +25,18 @@ LLVM_LIBC_FUNCTION(int, scandir,
                     int (*sel)(const struct dirent *),
                     int (*compar)(const struct dirent **,
                                   const struct dirent **))) {
-  (void)dir;
   (void)namelist;
   (void)sel;
   (void)compar;
-  return -1;
+
+  DIR *dir_fd = opendir(dir);
+  if (dir_fd == nullptr) {
+    // opendir set errno
+    return -1;
+  }
+
+
+  return 0;
 }
 
 } // namespace LIBC_NAMESPACE_DECL
diff --git a/libc/test/src/dirent/CMakeLists.txt b/libc/test/src/dirent/CMakeLists.txt
index fd56b23d2c4ed2..309045f8a9536f 100644
--- a/libc/test/src/dirent/CMakeLists.txt
+++ b/libc/test/src/dirent/CMakeLists.txt
@@ -47,4 +47,6 @@ add_libc_test(
   DEPENDS
     libc.hdr.types.struct_dirent
     libc.src.dirent.scandir
+    libc.test.UnitTest.ErrnoCheckingTest
+    libc.test.UnitTest.ErrnoSetterMatcher
 )
diff --git a/libc/test/src/dirent/scandir_test.cpp b/libc/test/src/dirent/scandir_test.cpp
index 34e5e83c598a01..5be2c9c636bdbf 100644
--- a/libc/test/src/dirent/scandir_test.cpp
+++ b/libc/test/src/dirent/scandir_test.cpp
@@ -13,9 +13,14 @@
 
 #include "hdr/types/struct_dirent.h"
 #include "src/dirent/scandir.h"
+#include "test/UnitTest/ErrnoCheckingTest.h"
+#include "test/UnitTest/ErrnoSetterMatcher.h"
 #include "test/UnitTest/Test.h"
 
-TEST(LlvmLibcScandirTest, DummyTest) {
+using LlvmLibcScandirTest = LIBC_NAMESPACE::testing::ErrnoCheckingTest;
+using LIBC_NAMESPACE::testing::ErrnoSetterMatcher::Succeeds;
+
+TEST_F(LlvmLibcScandirTest, TestBadDirname) {
   struct dirent **namelist;
-  ASSERT_NE(LIBC_NAMESPACE::scandir(".", &namelist, NULL, NULL), -1);
+  ASSERT_THAT(LIBC_NAMESPACE::scandir("", &namelist, NULL, NULL), Fails(ENOTDIR, -1));
 }

>From 082197577ce29b5f4d30bc8d8634b8d8da1a9f60 Mon Sep 17 00:00:00 2001
From: Vadim Kotov <kotov at google.com>
Date: Tue, 8 Sep 2026 11:29:21 -0700
Subject: [PATCH 04/30] Fix a rebase typo

---
 libc/config/linux/x86_64/entrypoints.txt | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/libc/config/linux/x86_64/entrypoints.txt b/libc/config/linux/x86_64/entrypoints.txt
index 2ff99649c2b141..bfbc59b0e0a48d 100644
--- a/libc/config/linux/x86_64/entrypoints.txt
+++ b/libc/config/linux/x86_64/entrypoints.txt
@@ -630,7 +630,7 @@ set(TARGET_LIBM_ENTRYPOINTS
     libc.src.fenv.fesetexceptflag
     libc.src.fenv.fesetround
     libc.src.fenv.fetestexcept
-    libc.src.fenv.fegestexceptflag
+    libc.src.fenv.fetestexceptflag
     libc.src.fenv.feupdateenv
 
     # math.h entrypoints

>From ced126aff4a8cc88e7676cc7d74c69b06d43069f Mon Sep 17 00:00:00 2001
From: Vadim Kotov <kotov at google.com>
Date: Tue, 8 Sep 2026 14:39:06 -0700
Subject: [PATCH 05/30] Create type headers for scandir filter and compare
 function pointers and update the unit test.

---
 libc/include/CMakeLists.txt                   |  2 ++
 libc/include/dirent.yaml                      |  6 ++++--
 libc/include/llvm-libc-types/CMakeLists.txt   |  2 ++
 .../llvm-libc-types/__scandir_compare_t.h     | 21 +++++++++++++++++++
 .../llvm-libc-types/__scandir_filter_t.h      | 21 +++++++++++++++++++
 libc/test/src/dirent/scandir_test.cpp         |  4 ++--
 6 files changed, 52 insertions(+), 4 deletions(-)
 create mode 100644 libc/include/llvm-libc-types/__scandir_compare_t.h
 create mode 100644 libc/include/llvm-libc-types/__scandir_filter_t.h

diff --git a/libc/include/CMakeLists.txt b/libc/include/CMakeLists.txt
index a45026de31ee5e..6b9060bdcfb2bc 100644
--- a/libc/include/CMakeLists.txt
+++ b/libc/include/CMakeLists.txt
@@ -47,6 +47,8 @@ add_header_macro(
   dirent.h
   DEPENDS
     .llvm_libc_common_h
+    .llvm-libc-types.__scandir_filter_t
+    .llvm-libc-types.__scandir_compare_t
     .llvm-libc-types.ino_t
     .llvm-libc-types.DIR
     .llvm-libc-types.struct_dirent
diff --git a/libc/include/dirent.yaml b/libc/include/dirent.yaml
index 8dc939fa1158a8..95957303270ab0 100644
--- a/libc/include/dirent.yaml
+++ b/libc/include/dirent.yaml
@@ -26,6 +26,8 @@ types:
   - type_name: reclen_t
   - type_name: size_t
   - type_name: ssize_t
+  - type_name: __scandir_filter_t
+  - type_name: __scandir_compare_t
 functions:
   - name: alphasort
     standards:
@@ -71,5 +73,5 @@ functions:
     arguments:
       - type: const char *
       - type: struct dirent ***
-      - type: int (*)(const struct dirent *)
-      - type: int (*)(const struct dirent **, const struct dirent **)
+      - type: __scandir_filter_t
+      - type: __scandir_compare_t
diff --git a/libc/include/llvm-libc-types/CMakeLists.txt b/libc/include/llvm-libc-types/CMakeLists.txt
index cc7b4df1202ff1..6464fdffa696bf 100644
--- a/libc/include/llvm-libc-types/CMakeLists.txt
+++ b/libc/include/llvm-libc-types/CMakeLists.txt
@@ -761,3 +761,5 @@ add_header(__action_fn_t
     .VISIT
 )
 add_header(__free_fn_t HDR __free_fn_t.h)
+add_header(__scandir_filter_t HDR __scandir_filter_t.h DEPENDS .struct_dirent)
+add_header(__scandir_compare_t HDR __scandir_compare_t.h DEPENDS .struct_dirent)
diff --git a/libc/include/llvm-libc-types/__scandir_compare_t.h b/libc/include/llvm-libc-types/__scandir_compare_t.h
new file mode 100644
index 00000000000000..0039188ee64709
--- /dev/null
+++ b/libc/include/llvm-libc-types/__scandir_compare_t.h
@@ -0,0 +1,21 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+///
+/// \file
+/// Definition of __scandir_compare_t type.
+///
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_LIBC_TYPES___SCANDIR_COMPARE_T_H
+#define LLVM_LIBC_TYPES___SCANDIR_COMPARE_T_H
+
+#include "struct_dirent.h"
+
+typedef int (*__scandir_compare_t)(const struct dirent **, const struct dirent **);
+
+#endif  // LLVM_LIBC_TYPES___SCANDIR_COMPARE_T_H
diff --git a/libc/include/llvm-libc-types/__scandir_filter_t.h b/libc/include/llvm-libc-types/__scandir_filter_t.h
new file mode 100644
index 00000000000000..204c950ee1e2c7
--- /dev/null
+++ b/libc/include/llvm-libc-types/__scandir_filter_t.h
@@ -0,0 +1,21 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+///
+/// \file
+/// Definition of __scandir_filter type.
+///
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_LIBC_TYPES___SCANDIR_FILTER_T_H
+#define LLVM_LIBC_TYPES___SCANDIR_FILTER_T_H
+
+#include "struct_dirent.h"
+
+typedef int (*__scandir_filter_t)(const struct dirent *);
+
+#endif  // LLVM_LIBC_TYPES___SCANDIR_FILTER_H
diff --git a/libc/test/src/dirent/scandir_test.cpp b/libc/test/src/dirent/scandir_test.cpp
index 5be2c9c636bdbf..0284da9270eb7f 100644
--- a/libc/test/src/dirent/scandir_test.cpp
+++ b/libc/test/src/dirent/scandir_test.cpp
@@ -17,10 +17,10 @@
 #include "test/UnitTest/ErrnoSetterMatcher.h"
 #include "test/UnitTest/Test.h"
 
+using namespace LIBC_NAMESPACE::testing::ErrnoSetterMatcher;
 using LlvmLibcScandirTest = LIBC_NAMESPACE::testing::ErrnoCheckingTest;
-using LIBC_NAMESPACE::testing::ErrnoSetterMatcher::Succeeds;
 
 TEST_F(LlvmLibcScandirTest, TestBadDirname) {
   struct dirent **namelist;
-  ASSERT_THAT(LIBC_NAMESPACE::scandir("", &namelist, NULL, NULL), Fails(ENOTDIR, -1));
+  ASSERT_THAT(LIBC_NAMESPACE::scandir("", &namelist, NULL, NULL), Fails(ENOENT, -1));
 }

>From 93b6358c0e1964152fa5ba6d925e4054ee52e9fb Mon Sep 17 00:00:00 2001
From: Vadim Kotov <kotov at google.com>
Date: Wed, 9 Sep 2026 17:04:19 -0700
Subject: [PATCH 06/30] Implement readdir loop.

---
 libc/src/dirent/CMakeLists.txt        |  3 +++
 libc/src/dirent/scandir.cpp           | 25 ++++++++++++++++++++++---
 libc/test/src/dirent/scandir_test.cpp |  5 +++++
 3 files changed, 30 insertions(+), 3 deletions(-)

diff --git a/libc/src/dirent/CMakeLists.txt b/libc/src/dirent/CMakeLists.txt
index 4ad2a8b286a039..6fe1d23ac702bb 100644
--- a/libc/src/dirent/CMakeLists.txt
+++ b/libc/src/dirent/CMakeLists.txt
@@ -59,7 +59,10 @@ add_entrypoint_object(
     libc.hdr.types.struct_dirent
     libc.src.__support.common
     libc.src.__support.macros.config
+    libc.src.dirent.closedir
     libc.src.dirent.opendir
+    libc.src.dirent.readdir
+    libc.src.errno.errno
 )
 
 add_entrypoint_object(
diff --git a/libc/src/dirent/scandir.cpp b/libc/src/dirent/scandir.cpp
index 45351d7aad0340..b8f720930801d7 100644
--- a/libc/src/dirent/scandir.cpp
+++ b/libc/src/dirent/scandir.cpp
@@ -16,7 +16,10 @@
 #include "hdr/types/struct_dirent.h"
 #include "src/__support/common.h"
 #include "src/__support/macros/config.h"
+#include "src/__support/libc_errno.h"
+#include "src/dirent/closedir.h"
 #include "src/dirent/opendir.h"
+#include "src/dirent/readdir.h"
 
 namespace LIBC_NAMESPACE_DECL {
 
@@ -26,15 +29,31 @@ LLVM_LIBC_FUNCTION(int, scandir,
                     int (*compar)(const struct dirent **,
                                   const struct dirent **))) {
   (void)namelist;
-  (void)sel;
   (void)compar;
 
-  DIR *dir_fd = opendir(dir);
+  DIR *dir_fd = LIBC_NAMESPACE::opendir(dir);
   if (dir_fd == nullptr) {
-    // opendir set errno
+    // errno set by opendir
     return -1;
   }
 
+  struct dirent *entry = nullptr;
+  do {
+    libc_errno = 0;
+    entry = LIBC_NAMESPACE::readdir(dir_fd);
+    if (libc_errno != 0) {
+      // errno set by readdir
+      return -1;
+    }
+
+    if (sel != nullptr && !sel(entry)) {
+      continue;
+    }
+
+  } while (entry != nullptr);
+
+
+  LIBC_NAMESPACE::closedir(dir_fd);
 
   return 0;
 }
diff --git a/libc/test/src/dirent/scandir_test.cpp b/libc/test/src/dirent/scandir_test.cpp
index 0284da9270eb7f..a7da06c9853de4 100644
--- a/libc/test/src/dirent/scandir_test.cpp
+++ b/libc/test/src/dirent/scandir_test.cpp
@@ -20,6 +20,11 @@
 using namespace LIBC_NAMESPACE::testing::ErrnoSetterMatcher;
 using LlvmLibcScandirTest = LIBC_NAMESPACE::testing::ErrnoCheckingTest;
 
+TEST_F(LlvmLibcScandirTest, TestDummy) {
+  struct dirent **namelist;
+  ASSERT_THAT(LIBC_NAMESPACE::scandir("/tmp", &namelist, NULL, NULL), Succeeds());
+}
+
 TEST_F(LlvmLibcScandirTest, TestBadDirname) {
   struct dirent **namelist;
   ASSERT_THAT(LIBC_NAMESPACE::scandir("", &namelist, NULL, NULL), Fails(ENOENT, -1));

>From d2fa1849f0921bf3d00bd2f2f28317f8ba17f972 Mon Sep 17 00:00:00 2001
From: Vadim Kotov <kotov at google.com>
Date: Fri, 11 Sep 2026 12:29:18 -0700
Subject: [PATCH 07/30] Minor changes and fixes, commit before sync.

---
 libc/include/CMakeLists.txt    |  2 +-
 libc/include/dirent.yaml       |  2 +-
 libc/src/dirent/CMakeLists.txt |  1 +
 libc/src/dirent/scandir.cpp    | 19 ++++++++++++++-----
 4 files changed, 17 insertions(+), 7 deletions(-)

diff --git a/libc/include/CMakeLists.txt b/libc/include/CMakeLists.txt
index 6b9060bdcfb2bc..da293857f45412 100644
--- a/libc/include/CMakeLists.txt
+++ b/libc/include/CMakeLists.txt
@@ -47,8 +47,8 @@ add_header_macro(
   dirent.h
   DEPENDS
     .llvm_libc_common_h
-    .llvm-libc-types.__scandir_filter_t
     .llvm-libc-types.__scandir_compare_t
+    .llvm-libc-types.__scandir_filter_t
     .llvm-libc-types.ino_t
     .llvm-libc-types.DIR
     .llvm-libc-types.struct_dirent
diff --git a/libc/include/dirent.yaml b/libc/include/dirent.yaml
index 95957303270ab0..ca8d6c6619127d 100644
--- a/libc/include/dirent.yaml
+++ b/libc/include/dirent.yaml
@@ -26,8 +26,8 @@ types:
   - type_name: reclen_t
   - type_name: size_t
   - type_name: ssize_t
-  - type_name: __scandir_filter_t
   - type_name: __scandir_compare_t
+  - type_name: __scandir_filter_t
 functions:
   - name: alphasort
     standards:
diff --git a/libc/src/dirent/CMakeLists.txt b/libc/src/dirent/CMakeLists.txt
index 6fe1d23ac702bb..e81d16e83bb6f7 100644
--- a/libc/src/dirent/CMakeLists.txt
+++ b/libc/src/dirent/CMakeLists.txt
@@ -63,6 +63,7 @@ add_entrypoint_object(
     libc.src.dirent.opendir
     libc.src.dirent.readdir
     libc.src.errno.errno
+    libc.src.stdlib.qsort_util
 )
 
 add_entrypoint_object(
diff --git a/libc/src/dirent/scandir.cpp b/libc/src/dirent/scandir.cpp
index b8f720930801d7..9c1d5c2ff82452 100644
--- a/libc/src/dirent/scandir.cpp
+++ b/libc/src/dirent/scandir.cpp
@@ -14,22 +14,24 @@
 #include "src/dirent/scandir.h"
 
 #include "hdr/types/struct_dirent.h"
+/*
+#include "include/llvm-libc-types/__scandir_compare_t.h"
+#include "include/llvm-libc-types/__scandir_filter_t.h"
+*/
 #include "src/__support/common.h"
 #include "src/__support/macros/config.h"
 #include "src/__support/libc_errno.h"
 #include "src/dirent/closedir.h"
 #include "src/dirent/opendir.h"
 #include "src/dirent/readdir.h"
+#include "src/stdlib/qsort_util.h"
 
 namespace LIBC_NAMESPACE_DECL {
 
 LLVM_LIBC_FUNCTION(int, scandir,
                    (const char *dir, struct dirent ***namelist,
-                    int (*sel)(const struct dirent *),
-                    int (*compar)(const struct dirent **,
-                                  const struct dirent **))) {
+                    __scandir_filter_t filter, __scandir_compare_t compare)) {
   (void)namelist;
-  (void)compar;
 
   DIR *dir_fd = LIBC_NAMESPACE::opendir(dir);
   if (dir_fd == nullptr) {
@@ -37,7 +39,12 @@ LLVM_LIBC_FUNCTION(int, scandir,
     return -1;
   }
 
+  struct dirent **namelist_local = {};
+  (void)namelist_local;
+
   struct dirent *entry = nullptr;
+  size_t count = 0;
+
   do {
     libc_errno = 0;
     entry = LIBC_NAMESPACE::readdir(dir_fd);
@@ -46,12 +53,14 @@ LLVM_LIBC_FUNCTION(int, scandir,
       return -1;
     }
 
-    if (sel != nullptr && !sel(entry)) {
+    if (filter != nullptr && !filter(entry)) {
       continue;
     }
 
   } while (entry != nullptr);
 
+  if (compare != nullptr) {
+  }
 
   LIBC_NAMESPACE::closedir(dir_fd);
 

>From e339c8e9b9302f22b5dd34bde2d188acd6e7b86e Mon Sep 17 00:00:00 2001
From: Vadim Kotov <kotov at google.com>
Date: Fri, 11 Sep 2026 16:22:18 -0700
Subject: [PATCH 08/30] Implement complete scandir functionality.

---
 libc/src/dirent/CMakeLists.txt |  3 ++
 libc/src/dirent/scandir.cpp    | 74 +++++++++++++++++++++++++++-------
 2 files changed, 62 insertions(+), 15 deletions(-)

diff --git a/libc/src/dirent/CMakeLists.txt b/libc/src/dirent/CMakeLists.txt
index e81d16e83bb6f7..befea755909d68 100644
--- a/libc/src/dirent/CMakeLists.txt
+++ b/libc/src/dirent/CMakeLists.txt
@@ -56,8 +56,11 @@ add_entrypoint_object(
   HDRS
     scandir.h
   DEPENDS
+    libc.hdr.types.size_t
     libc.hdr.types.struct_dirent
+    libc.src.__support.alloc_checker
     libc.src.__support.common
+    libc.src.__support.CPP.vector
     libc.src.__support.macros.config
     libc.src.dirent.closedir
     libc.src.dirent.opendir
diff --git a/libc/src/dirent/scandir.cpp b/libc/src/dirent/scandir.cpp
index 9c1d5c2ff82452..9c693bfc23ba39 100644
--- a/libc/src/dirent/scandir.cpp
+++ b/libc/src/dirent/scandir.cpp
@@ -7,61 +7,105 @@
 //===----------------------------------------------------------------------===//
 ///
 /// \file
-/// Implementation of getpriority.
+/// Implementation of scandir.
 ///
 //===----------------------------------------------------------------------===//
 
 #include "src/dirent/scandir.h"
 
+#include "hdr/types/size_t.h"
 #include "hdr/types/struct_dirent.h"
-/*
-#include "include/llvm-libc-types/__scandir_compare_t.h"
-#include "include/llvm-libc-types/__scandir_filter_t.h"
-*/
+#include "src/__support/alloc-checker.h"
 #include "src/__support/common.h"
 #include "src/__support/macros/config.h"
 #include "src/__support/libc_errno.h"
+#include "src/__support/CPP/vector.h"
 #include "src/dirent/closedir.h"
 #include "src/dirent/opendir.h"
 #include "src/dirent/readdir.h"
 #include "src/stdlib/qsort_util.h"
+#include "src/string/memcpy.h"
 
 namespace LIBC_NAMESPACE_DECL {
 
+void free_entries(LIBC_NAMESPACE::cpp::vector<struct dirent *> &entries) {
+  for (struct dirent *entry: entries) {
+    delete entry;
+  }
+}
+
 LLVM_LIBC_FUNCTION(int, scandir,
                    (const char *dir, struct dirent ***namelist,
                     __scandir_filter_t filter, __scandir_compare_t compare)) {
-  (void)namelist;
-
   DIR *dir_fd = LIBC_NAMESPACE::opendir(dir);
   if (dir_fd == nullptr) {
     // errno set by opendir
     return -1;
   }
 
-  struct dirent **namelist_local = {};
-  (void)namelist_local;
-
-  struct dirent *entry = nullptr;
-  size_t count = 0;
+  LIBC_NAMESPACE::cpp::vector<struct dirent*> entries;
 
-  do {
+  while (true) {
     libc_errno = 0;
-    entry = LIBC_NAMESPACE::readdir(dir_fd);
+    struct dirent *entry = LIBC_NAMESPACE::readdir(dir_fd);
     if (libc_errno != 0) {
+      free_entries(entries);
+      LIBC_NAMESPACE::closedir(dir_fd);
       // errno set by readdir
       return -1;
     }
 
+    if (entry == nullptr) {
+      break;
+    }
+
     if (filter != nullptr && !filter(entry)) {
       continue;
     }
 
-  } while (entry != nullptr);
+    AllocChecker ac;
+    struct dirent *new_entry = new (ac) struct dirent;
+    if (!ac || new_entry == NULL) {
+      free_entries(entries);
+      LIBC_NAMESPACE::closedir(dir_fd);
+      libc_errno = ENOMEM;
+      return -1;
+    }
+
+    LIBC_NAMESPACE::memcpy(new_entry, entry, sizeof(struct dirent));
+
+    if (!entries.push_back(new_entry)) {
+      free_entries(entries);
+      LIBC_NAMESPACE::closedir(dir_fd);
+      libc_errno = ENOMEM;
+      return -1;
+    }
+  }
 
   if (compare != nullptr) {
+    auto cmp_fn = [compare](const void *a, const void *b) {
+      auto left = static_cast<const struct dirent **>(const_cast<void *>(a));
+      auto right = static_cast<const struct dirent **>(const_cast<void *>(b));
+      return compare(left, right);
+    };
+    internal::unstable_sort(entries.data(), entries.size(), sizeof(struct dirent*), cmp_fn);
+  }
+
+  AllocChecker ac;
+  struct dirent **result = new (ac) struct dirent*[entries.size()];
+  if (!ac) {
+    free_entries(entries);
+    LIBC_NAMESPACE::closedir(dir_fd);
+    libc_errno = ENOMEM;
+    return -1;
   }
 
+  for (size_t i = 0; i < entries.size(); ++i) {
+    result[i] = entries[i];
+  }
+
+  *namelist = result;
+
   LIBC_NAMESPACE::closedir(dir_fd);
 
   return 0;

>From 95fcbd25855303e0df1e1fee29c01bbda68171a2 Mon Sep 17 00:00:00 2001
From: Vadim Kotov <kotov at google.com>
Date: Fri, 11 Sep 2026 17:14:45 -0700
Subject: [PATCH 09/30] Fix return value and refactor scandir.

---
 libc/src/dirent/scandir.cpp | 52 ++++++++++++++-----------------------
 1 file changed, 20 insertions(+), 32 deletions(-)

diff --git a/libc/src/dirent/scandir.cpp b/libc/src/dirent/scandir.cpp
index 9c693bfc23ba39..ab64961ab7276a 100644
--- a/libc/src/dirent/scandir.cpp
+++ b/libc/src/dirent/scandir.cpp
@@ -28,12 +28,6 @@
 
 namespace LIBC_NAMESPACE_DECL {
 
-void free_entries(LIBC_NAMESPACE::cpp::vector<struct dirent *> &entries) {
-  for (struct dirent *entry: entries) {
-    delete entry;
-  }
-}
-
 LLVM_LIBC_FUNCTION(int, scandir,
                    (const char *dir, struct dirent ***namelist,
                     __scandir_filter_t filter, __scandir_compare_t compare)) {
@@ -48,14 +42,8 @@ LLVM_LIBC_FUNCTION(int, scandir,
   while (true) {
     libc_errno = 0;
     struct dirent *entry = LIBC_NAMESPACE::readdir(dir_fd);
-    if (libc_errno != 0) {
-      free_entries(entries);
-      LIBC_NAMESPACE::closedir(dir_fd);
-      // errno set by readdir
-      return -1;
-    }
-
     if (entry == nullptr) {
+      // If the readdir call failed it set errno
       break;
     }
 
@@ -66,20 +54,32 @@ LLVM_LIBC_FUNCTION(int, scandir,
     AllocChecker ac;
     struct dirent *new_entry = new (ac) struct dirent;
     if (!ac || new_entry == NULL) {
-      free_entries(entries);
-      LIBC_NAMESPACE::closedir(dir_fd);
       libc_errno = ENOMEM;
-      return -1;
+      break;
     }
 
     LIBC_NAMESPACE::memcpy(new_entry, entry, sizeof(struct dirent));
 
     if (!entries.push_back(new_entry)) {
-      free_entries(entries);
-      LIBC_NAMESPACE::closedir(dir_fd);
       libc_errno = ENOMEM;
-      return -1;
+      break;
+    }
+  }
+
+  LIBC_NAMESPACE::closedir(dir_fd);
+
+
+  AllocChecker ac;
+  struct dirent **result = new (ac) struct dirent*[entries.size()];
+  if (!ac) {
+    libc_errno = ENOMEM;
+  }
+
+  if (libc_errno != 0) {
+    for (struct dirent *entry: entries) {
+      delete entry;
     }
+    return -1;
   }
 
   if (compare != nullptr) {
@@ -91,24 +91,12 @@ LLVM_LIBC_FUNCTION(int, scandir,
     internal::unstable_sort(entries.data(), entries.size(), sizeof(struct dirent*), cmp_fn);
   }
 
-  AllocChecker ac;
-  struct dirent **result = new (ac) struct dirent*[entries.size()];
-  if (!ac) {
-    free_entries(entries);
-    LIBC_NAMESPACE::closedir(dir_fd);
-    libc_errno = ENOMEM;
-    return -1;
-  }
-
   for (size_t i = 0; i < entries.size(); ++i) {
     result[i] = entries[i];
   }
 
   *namelist = result;
-
-  LIBC_NAMESPACE::closedir(dir_fd);
-
-  return 0;
+  return static_cast<int>(entries.size());
 }
 
 } // namespace LIBC_NAMESPACE_DECL

>From fa18b25ef03af457ca5a6e93576ec09fd847ebef Mon Sep 17 00:00:00 2001
From: Vadim Kotov <kotov at google.com>
Date: Fri, 11 Sep 2026 17:45:22 -0700
Subject: [PATCH 10/30] Implement scandir unit test skeleton and helper
 functions.

---
 libc/test/src/dirent/CMakeLists.txt   |  7 ++++
 libc/test/src/dirent/scandir_test.cpp | 54 ++++++++++++++++++++++++++-
 2 files changed, 59 insertions(+), 2 deletions(-)

diff --git a/libc/test/src/dirent/CMakeLists.txt b/libc/test/src/dirent/CMakeLists.txt
index 309045f8a9536f..1dc41ebd681d96 100644
--- a/libc/test/src/dirent/CMakeLists.txt
+++ b/libc/test/src/dirent/CMakeLists.txt
@@ -46,7 +46,14 @@ add_libc_test(
     scandir_test.cpp
   DEPENDS
     libc.hdr.types.struct_dirent
+    libc.src.__support.OSUtil.path
     libc.src.dirent.scandir
+    libc.src.stdio.asprintf
+    libc.src.stdio.fopen
+    libc.src.stdio.fclose
+    libc.src.stdlib.mkdtemp
+    libc.src.string.strdup
+    libc.src.unistd.rmdir
     libc.test.UnitTest.ErrnoCheckingTest
     libc.test.UnitTest.ErrnoSetterMatcher
 )
diff --git a/libc/test/src/dirent/scandir_test.cpp b/libc/test/src/dirent/scandir_test.cpp
index a7da06c9853de4..b06903cab1ab7e 100644
--- a/libc/test/src/dirent/scandir_test.cpp
+++ b/libc/test/src/dirent/scandir_test.cpp
@@ -12,7 +12,14 @@
 //===----------------------------------------------------------------------===//
 
 #include "hdr/types/struct_dirent.h"
+#include "src/__support/OSUtil/path.h"
 #include "src/dirent/scandir.h"
+#include "src/stdio/asprintf.h"
+#include "src/stdio/fopen.h"
+#include "src/stdio/fclose.h"
+#include "src/stdlib/mkdtemp.h"
+#include "src/string/strdup.h"
+#include "src/unistd/rmdir.h"
 #include "test/UnitTest/ErrnoCheckingTest.h"
 #include "test/UnitTest/ErrnoSetterMatcher.h"
 #include "test/UnitTest/Test.h"
@@ -20,11 +27,54 @@
 using namespace LIBC_NAMESPACE::testing::ErrnoSetterMatcher;
 using LlvmLibcScandirTest = LIBC_NAMESPACE::testing::ErrnoCheckingTest;
 
-TEST_F(LlvmLibcScandirTest, TestDummy) {
+constexpr char TEMPLATE[] = "tmp_XXXXXX";
+
+// A dir alwasys has '.' and '..' in it.
+constexpr int MINIMUM_ENTRIES = 2;
+
+bool create_file(char *dir, const char *name) {
+  char *path = nullptr;
+
+  if (LIBC_NAMESPACE::asprintf(&path, "%s%c%s", dir, LIBC_NAMESPACE::path::SEPARATOR, name) == -1) {
+    return false;
+  }
+
+  FILE *file = LIBC_NAMESPACE::fopen(path, "w");
+  if (file == nullptr) {
+    return false;
+  }
+
+  if (LIBC_NAMESPACE::fclose(file) == -1) {
+    return false;
+  }
+
+  return true;
+}
+
+
+TEST_F(LlvmLibcScandirTest, TestBasic) {
+
+  char *tmpl = LIBC_NAMESPACE::strdup(libc_make_test_file_path(TEMPLATE));
+  ASSERT_NE(tmpl, nullptr);
+  ASSERT_THAT(LIBC_NAMESPACE::mkdtemp(tmpl), Succeeds(tmpl));
+
+  const char *filename_a = libc_make_test_file_path("a");
+  const char *filename_b = libc_make_test_file_path("file_b");
+
+  ASSERT_TRUE(create_file(tmpl, filename_a));
+  ASSERT_TRUE(create_file(tmpl, filename_b));
+
   struct dirent **namelist;
-  ASSERT_THAT(LIBC_NAMESPACE::scandir("/tmp", &namelist, NULL, NULL), Succeeds());
+  ASSERT_THAT(LIBC_NAMESPACE::scandir(tmpl, &namelist, NULL, NULL), Succeeds(MINIMUM_ENTRIES + 2));
+
+  // TODO: Implement file deletion!
+  /*
+  ASSERT_THAT(LIBC_NAMESPACE::rmdir(tmpl), Succeeds());
+  free(tmpl);
+  */
 }
 
+
 TEST_F(LlvmLibcScandirTest, TestBadDirname) {
   struct dirent **namelist;
   ASSERT_THAT(LIBC_NAMESPACE::scandir("", &namelist, NULL, NULL), Fails(ENOENT, -1));

>From 4e4adc1740ec6bf52fc49dd2d587c232e0ba32f4 Mon Sep 17 00:00:00 2001
From: Vadim Kotov <kotov at google.com>
Date: Sat, 12 Sep 2026 16:23:43 -0700
Subject: [PATCH 11/30] Move scandir implementation to linux/ and clean up
 empty dir test.

---
 libc/src/dirent/CMakeLists.txt          | 32 ++++++++++---------------
 libc/src/dirent/linux/CMakeLists.txt    | 19 +++++++++++++++
 libc/src/dirent/{ => linux}/scandir.cpp |  4 +++-
 libc/test/src/dirent/CMakeLists.txt     |  1 +
 libc/test/src/dirent/scandir_test.cpp   | 31 ++++++++++++++----------
 5 files changed, 54 insertions(+), 33 deletions(-)
 create mode 100644 libc/src/dirent/linux/CMakeLists.txt
 rename libc/src/dirent/{ => linux}/scandir.cpp (93%)

diff --git a/libc/src/dirent/CMakeLists.txt b/libc/src/dirent/CMakeLists.txt
index befea755909d68..b5237333e29312 100644
--- a/libc/src/dirent/CMakeLists.txt
+++ b/libc/src/dirent/CMakeLists.txt
@@ -1,3 +1,8 @@
+if(EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/${LIBC_TARGET_OS})
+  add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/${LIBC_TARGET_OS})
+endif()
+
+
 add_entrypoint_object(
   opendir
   SRCS
@@ -49,26 +54,6 @@ add_entrypoint_object(
     libc.src.errno.errno
 )
 
-add_entrypoint_object(
-  scandir
-  SRCS
-    scandir.cpp
-  HDRS
-    scandir.h
-  DEPENDS
-    libc.hdr.types.size_t
-    libc.hdr.types.struct_dirent
-    libc.src.__support.alloc_checker
-    libc.src.__support.common
-    libc.src.__support.CPP.vector
-    libc.src.__support.macros.config
-    libc.src.dirent.closedir
-    libc.src.dirent.opendir
-    libc.src.dirent.readdir
-    libc.src.errno.errno
-    libc.src.stdlib.qsort_util
-)
-
 add_entrypoint_object(
   fdopendir
   SRCS
@@ -82,3 +67,10 @@ add_entrypoint_object(
     libc.src.__support.File.dir
     libc.src.errno.errno
 )
+
+add_entrypoint_object(
+  scandir
+  ALIAS
+  DEPENDS
+    .${LIBC_TARGET_OS}.scandir
+)
diff --git a/libc/src/dirent/linux/CMakeLists.txt b/libc/src/dirent/linux/CMakeLists.txt
new file mode 100644
index 00000000000000..e68c3d0e240c63
--- /dev/null
+++ b/libc/src/dirent/linux/CMakeLists.txt
@@ -0,0 +1,19 @@
+add_entrypoint_object(
+  scandir
+  SRCS
+    scandir.cpp
+  HDRS
+    ../scandir.h
+  DEPENDS
+    libc.hdr.types.size_t
+    libc.hdr.types.struct_dirent
+    libc.src.__support.alloc_checker
+    libc.src.__support.common
+    libc.src.__support.CPP.vector
+    libc.src.__support.macros.config
+    libc.src.dirent.closedir
+    libc.src.dirent.opendir
+    libc.src.dirent.readdir
+    libc.src.errno.errno
+    libc.src.stdlib.qsort_util
+)
diff --git a/libc/src/dirent/scandir.cpp b/libc/src/dirent/linux/scandir.cpp
similarity index 93%
rename from libc/src/dirent/scandir.cpp
rename to libc/src/dirent/linux/scandir.cpp
index ab64961ab7276a..d1f16120429867 100644
--- a/libc/src/dirent/scandir.cpp
+++ b/libc/src/dirent/linux/scandir.cpp
@@ -58,7 +58,9 @@ LLVM_LIBC_FUNCTION(int, scandir,
       break;
     }
 
-    LIBC_NAMESPACE::memcpy(new_entry, entry, sizeof(struct dirent));
+    // struct dirent contains an equivalent of flexible array memeber,
+    // which makes sizeof unreliable, hence we use d_reclen.
+    LIBC_NAMESPACE::memcpy(new_entry, entry, entry->d_reclen);
 
     if (!entries.push_back(new_entry)) {
       libc_errno = ENOMEM;
diff --git a/libc/test/src/dirent/CMakeLists.txt b/libc/test/src/dirent/CMakeLists.txt
index 1dc41ebd681d96..c1e7d3288bfad5 100644
--- a/libc/test/src/dirent/CMakeLists.txt
+++ b/libc/test/src/dirent/CMakeLists.txt
@@ -53,6 +53,7 @@ add_libc_test(
     libc.src.stdio.fclose
     libc.src.stdlib.mkdtemp
     libc.src.string.strdup
+    libc.src.string.strncmp
     libc.src.unistd.rmdir
     libc.test.UnitTest.ErrnoCheckingTest
     libc.test.UnitTest.ErrnoSetterMatcher
diff --git a/libc/test/src/dirent/scandir_test.cpp b/libc/test/src/dirent/scandir_test.cpp
index b06903cab1ab7e..377412c5cd52a7 100644
--- a/libc/test/src/dirent/scandir_test.cpp
+++ b/libc/test/src/dirent/scandir_test.cpp
@@ -19,6 +19,7 @@
 #include "src/stdio/fclose.h"
 #include "src/stdlib/mkdtemp.h"
 #include "src/string/strdup.h"
+#include "src/string/strncmp.h"
 #include "src/unistd/rmdir.h"
 #include "test/UnitTest/ErrnoCheckingTest.h"
 #include "test/UnitTest/ErrnoSetterMatcher.h"
@@ -30,7 +31,7 @@ using LlvmLibcScandirTest = LIBC_NAMESPACE::testing::ErrnoCheckingTest;
 constexpr char TEMPLATE[] = "tmp_XXXXXX";
 
 // A dir alwasys has '.' and '..' in it.
-constexpr int MINIMUM_ENTRIES = 2;
+constexpr int ENTRIES_MIN = 2;
 
 bool create_file(char *dir, const char *name) {
   char *path = nullptr;
@@ -38,6 +39,7 @@ bool create_file(char *dir, const char *name) {
   if (LIBC_NAMESPACE::asprintf(&path, "%s%c%s", dir, LIBC_NAMESPACE::path::SEPARATOR, name) == -1) {
     return false;
   }
+  free(path);
 
   FILE *file = LIBC_NAMESPACE::fopen(path, "w");
   if (file == nullptr) {
@@ -52,26 +54,31 @@ bool create_file(char *dir, const char *name) {
 }
 
 
-TEST_F(LlvmLibcScandirTest, TestBasic) {
+TEST_F(LlvmLibcScandirTest, TestEmptyDir) {
 
   char *tmpl = LIBC_NAMESPACE::strdup(libc_make_test_file_path(TEMPLATE));
   ASSERT_NE(tmpl, nullptr);
   ASSERT_THAT(LIBC_NAMESPACE::mkdtemp(tmpl), Succeeds(tmpl));
 
-  const char *filename_a = libc_make_test_file_path("a");
-  const char *filename_b = libc_make_test_file_path("file_b");
-
-  ASSERT_TRUE(create_file(tmpl, filename_a));
-  ASSERT_TRUE(create_file(tmpl, filename_b));
-
   struct dirent **namelist;
-  ASSERT_THAT(LIBC_NAMESPACE::scandir(tmpl, &namelist, NULL, NULL), Succeeds(MINIMUM_ENTRIES + 2));
+  ASSERT_THAT(LIBC_NAMESPACE::scandir(tmpl, &namelist, NULL, NULL), Succeeds(ENTRIES_MIN));
+  // ASSERT_STREQ(namelist[1]->d_name, ".");
+  // Order of namelist is not guaranteed so we can't easily use ASSERT_STREQ
+  ASSERT_TRUE(
+      (LIBC_NAMESPACE::strncmp(namelist[0]->d_name, ".",  1) == 0 &&
+       LIBC_NAMESPACE::strncmp(namelist[1]->d_name, "..", 2) == 0) ||
+      (LIBC_NAMESPACE::strncmp(namelist[0]->d_name, "..", 2) == 0 &&
+       LIBC_NAMESPACE::strncmp(namelist[1]->d_name, ".",  1) == 0));
+
+  // We also test that both ordering can't be true at the same time.
+  ASSERT_FALSE(
+      (LIBC_NAMESPACE::strncmp(namelist[0]->d_name, ".",  1) == 0 &&
+       LIBC_NAMESPACE::strncmp(namelist[1]->d_name, "..", 2) == 0) &&
+      (LIBC_NAMESPACE::strncmp(namelist[0]->d_name, "..", 2) == 0 &&
+       LIBC_NAMESPACE::strncmp(namelist[1]->d_name, ".",  1) == 0));
 
-  // TODO: Implement file deletion!
-  /*
   ASSERT_THAT(LIBC_NAMESPACE::rmdir(tmpl), Succeeds());
   free(tmpl);
-  */
 }
 
 

>From 5dec36b120d4006f76c09e1524fd4a48c4afa250 Mon Sep 17 00:00:00 2001
From: Vadim Kotov <kotov at google.com>
Date: Sat, 12 Sep 2026 17:10:41 -0700
Subject: [PATCH 12/30] Use malloc instead of AllocChecker in scandir

---
 libc/src/dirent/linux/CMakeLists.txt |  3 ++-
 libc/src/dirent/linux/scandir.cpp    | 38 ++++++++++++++++------------
 2 files changed, 24 insertions(+), 17 deletions(-)

diff --git a/libc/src/dirent/linux/CMakeLists.txt b/libc/src/dirent/linux/CMakeLists.txt
index e68c3d0e240c63..71ec9be5766236 100644
--- a/libc/src/dirent/linux/CMakeLists.txt
+++ b/libc/src/dirent/linux/CMakeLists.txt
@@ -5,9 +5,10 @@ add_entrypoint_object(
   HDRS
     ../scandir.h
   DEPENDS
+    libc.hdr.func.free
+    libc.hdr.func.malloc
     libc.hdr.types.size_t
     libc.hdr.types.struct_dirent
-    libc.src.__support.alloc_checker
     libc.src.__support.common
     libc.src.__support.CPP.vector
     libc.src.__support.macros.config
diff --git a/libc/src/dirent/linux/scandir.cpp b/libc/src/dirent/linux/scandir.cpp
index d1f16120429867..37fd06075b136c 100644
--- a/libc/src/dirent/linux/scandir.cpp
+++ b/libc/src/dirent/linux/scandir.cpp
@@ -13,9 +13,10 @@
 
 #include "src/dirent/scandir.h"
 
+#include "hdr/func/free.h"
+#include "hdr/func/malloc.h"
 #include "hdr/types/size_t.h"
 #include "hdr/types/struct_dirent.h"
-#include "src/__support/alloc-checker.h"
 #include "src/__support/common.h"
 #include "src/__support/macros/config.h"
 #include "src/__support/libc_errno.h"
@@ -23,6 +24,7 @@
 #include "src/dirent/closedir.h"
 #include "src/dirent/opendir.h"
 #include "src/dirent/readdir.h"
+#include "src/stdlib/malloc.h"
 #include "src/stdlib/qsort_util.h"
 #include "src/string/memcpy.h"
 
@@ -37,6 +39,7 @@ LLVM_LIBC_FUNCTION(int, scandir,
     return -1;
   }
 
+  int saved_errno = 0;
   LIBC_NAMESPACE::cpp::vector<struct dirent*> entries;
 
   while (true) {
@@ -44,43 +47,46 @@ LLVM_LIBC_FUNCTION(int, scandir,
     struct dirent *entry = LIBC_NAMESPACE::readdir(dir_fd);
     if (entry == nullptr) {
       // If the readdir call failed it set errno
+      saved_errno = libc_errno;
       break;
     }
 
+    // Note, filter may modify errno
     if (filter != nullptr && !filter(entry)) {
       continue;
     }
 
-    AllocChecker ac;
-    struct dirent *new_entry = new (ac) struct dirent;
-    if (!ac || new_entry == NULL) {
-      libc_errno = ENOMEM;
-      break;
+    // struct dirent contains an equivalent of flexible array memeber we
+    // allocate with malloc and use d_reclen as size.
+    struct dirent *new_entry = static_cast<struct dirent*>(::malloc(entry->d_reclen));
+    if (new_entry == nullptr) {
+      saved_errno = ENOMEM;
     }
-
-    // struct dirent contains an equivalent of flexible array memeber,
-    // which makes sizeof unreliable, hence we use d_reclen.
     LIBC_NAMESPACE::memcpy(new_entry, entry, entry->d_reclen);
 
     if (!entries.push_back(new_entry)) {
-      libc_errno = ENOMEM;
+      free(new_entry);
+      saved_errno = ENOMEM;
       break;
     }
   }
 
+  // Closedir may modify errno and set it to EBADF, which is not amongst
+  // POSIX-defined error codes for scandir. So we ignore closedir's errno.
   LIBC_NAMESPACE::closedir(dir_fd);
 
+  struct dirent **result = static_cast<struct dirent**>(
+      ::malloc(entries.size() * sizeof(struct dirent *)));
 
-  AllocChecker ac;
-  struct dirent **result = new (ac) struct dirent*[entries.size()];
-  if (!ac) {
-    libc_errno = ENOMEM;
+  if (result == nullptr) {
+    saved_errno = ENOMEM;
   }
 
-  if (libc_errno != 0) {
+  if (saved_errno != 0) {
     for (struct dirent *entry: entries) {
-      delete entry;
+      ::free(entry);
     }
+    libc_errno = saved_errno;
     return -1;
   }
 

>From b678e7d34d2a1d744cea6e0f88d1154757cbdbf6 Mon Sep 17 00:00:00 2001
From: Vadim Kotov <kotov at google.com>
Date: Sat, 12 Sep 2026 17:54:21 -0700
Subject: [PATCH 13/30] Implement basic filter and compare unit tests for
 scandir

---
 libc/test/src/dirent/CMakeLists.txt   |  2 +
 libc/test/src/dirent/scandir_test.cpp | 77 ++++++++++++++++++++++-----
 2 files changed, 67 insertions(+), 12 deletions(-)

diff --git a/libc/test/src/dirent/CMakeLists.txt b/libc/test/src/dirent/CMakeLists.txt
index c1e7d3288bfad5..81bc4dd19bd5b9 100644
--- a/libc/test/src/dirent/CMakeLists.txt
+++ b/libc/test/src/dirent/CMakeLists.txt
@@ -51,7 +51,9 @@ add_libc_test(
     libc.src.stdio.asprintf
     libc.src.stdio.fopen
     libc.src.stdio.fclose
+    libc.src.stdio.remove
     libc.src.stdlib.mkdtemp
+    libc.src.string.strcoll
     libc.src.string.strdup
     libc.src.string.strncmp
     libc.src.unistd.rmdir
diff --git a/libc/test/src/dirent/scandir_test.cpp b/libc/test/src/dirent/scandir_test.cpp
index 377412c5cd52a7..2acc0e3ed47c40 100644
--- a/libc/test/src/dirent/scandir_test.cpp
+++ b/libc/test/src/dirent/scandir_test.cpp
@@ -17,7 +17,9 @@
 #include "src/stdio/asprintf.h"
 #include "src/stdio/fopen.h"
 #include "src/stdio/fclose.h"
+#include "src/stdio/remove.h"
 #include "src/stdlib/mkdtemp.h"
+#include "src/string/strcoll.h"
 #include "src/string/strdup.h"
 #include "src/string/strncmp.h"
 #include "src/unistd/rmdir.h"
@@ -33,15 +35,14 @@ constexpr char TEMPLATE[] = "tmp_XXXXXX";
 // A dir alwasys has '.' and '..' in it.
 constexpr int ENTRIES_MIN = 2;
 
-bool create_file(char *dir, const char *name) {
+char *join_path(char *dir, const char *filename) {
   char *path = nullptr;
-
-  if (LIBC_NAMESPACE::asprintf(&path, "%s%c%s", dir, LIBC_NAMESPACE::path::SEPARATOR, name) == -1) {
-    return false;
+  if (LIBC_NAMESPACE::asprintf(&path, "%s%c%s", dir, LIBC_NAMESPACE::path::SEPARATOR, filename) == -1) {
+    return nullptr;
   }
-  free(path);
-
-  FILE *file = LIBC_NAMESPACE::fopen(path, "w");
+  return path;
+}
+bool create_empty_file(char *path) { FILE *file = LIBC_NAMESPACE::fopen(path, "w");
   if (file == nullptr) {
     return false;
   }
@@ -49,20 +50,25 @@ bool create_file(char *dir, const char *name) {
   if (LIBC_NAMESPACE::fclose(file) == -1) {
     return false;
   }
-
   return true;
 }
 
 
-TEST_F(LlvmLibcScandirTest, TestEmptyDir) {
+int alphasort(const struct dirent **a, const struct dirent **b) {
+  return LIBC_NAMESPACE::strcoll((*a)->d_name, (*b)->d_name);
+}
+
+int skip_hidden(const struct dirent *entry) {
+    return entry->d_name[0] != '.';
+}
 
+TEST_F(LlvmLibcScandirTest, TestEmptyDir) {
   char *tmpl = LIBC_NAMESPACE::strdup(libc_make_test_file_path(TEMPLATE));
   ASSERT_NE(tmpl, nullptr);
   ASSERT_THAT(LIBC_NAMESPACE::mkdtemp(tmpl), Succeeds(tmpl));
 
   struct dirent **namelist;
-  ASSERT_THAT(LIBC_NAMESPACE::scandir(tmpl, &namelist, NULL, NULL), Succeeds(ENTRIES_MIN));
-  // ASSERT_STREQ(namelist[1]->d_name, ".");
+  ASSERT_THAT(LIBC_NAMESPACE::scandir(tmpl, &namelist, nullptr, nullptr), Succeeds(ENTRIES_MIN));
   // Order of namelist is not guaranteed so we can't easily use ASSERT_STREQ
   ASSERT_TRUE(
       (LIBC_NAMESPACE::strncmp(namelist[0]->d_name, ".",  1) == 0 &&
@@ -70,7 +76,7 @@ TEST_F(LlvmLibcScandirTest, TestEmptyDir) {
       (LIBC_NAMESPACE::strncmp(namelist[0]->d_name, "..", 2) == 0 &&
        LIBC_NAMESPACE::strncmp(namelist[1]->d_name, ".",  1) == 0));
 
-  // We also test that both ordering can't be true at the same time.
+  // We also test that both orderings can't be true at the same time.
   ASSERT_FALSE(
       (LIBC_NAMESPACE::strncmp(namelist[0]->d_name, ".",  1) == 0 &&
        LIBC_NAMESPACE::strncmp(namelist[1]->d_name, "..", 2) == 0) &&
@@ -81,6 +87,53 @@ TEST_F(LlvmLibcScandirTest, TestEmptyDir) {
   free(tmpl);
 }
 
+TEST_F(LlvmLibcScandirTest, TestDirFilter) {
+  char *tmpl = LIBC_NAMESPACE::strdup(libc_make_test_file_path(TEMPLATE));
+  ASSERT_NE(tmpl, nullptr);
+  ASSERT_THAT(LIBC_NAMESPACE::mkdtemp(tmpl), Succeeds(tmpl));
+
+  struct dirent **namelist;
+  ASSERT_THAT(LIBC_NAMESPACE::scandir(tmpl, &namelist, skip_hidden, nullptr), Succeeds(0));
+
+  ASSERT_THAT(LIBC_NAMESPACE::rmdir(tmpl), Succeeds());
+  free(tmpl);
+}
+
+TEST_F(LlvmLibcScandirTest, TestDirSorted) {
+  char *tmpl = LIBC_NAMESPACE::strdup(libc_make_test_file_path(TEMPLATE));
+  ASSERT_NE(tmpl, nullptr);
+  ASSERT_THAT(LIBC_NAMESPACE::mkdtemp(tmpl), Succeeds(tmpl));
+
+  char *path_d = join_path(tmpl, "d");
+  ASSERT_TRUE(path_d != nullptr);
+  ASSERT_TRUE(create_empty_file(path_d));
+
+  char *path_a = join_path(tmpl, "a");
+  ASSERT_TRUE(path_a != nullptr);
+  ASSERT_TRUE(create_empty_file(path_a));
+
+  char *path_1 = join_path(tmpl, "1");
+  ASSERT_TRUE(path_1 != nullptr);
+  ASSERT_TRUE(create_empty_file(path_1));
+
+  struct dirent **namelist;
+  ASSERT_THAT(LIBC_NAMESPACE::scandir(tmpl, &namelist, skip_hidden, alphasort), Succeeds(3));
+
+  ASSERT_STREQ(namelist[0]->d_name, "1");
+  ASSERT_STREQ(namelist[1]->d_name, "a");
+  ASSERT_STREQ(namelist[2]->d_name, "d");
+
+  ASSERT_THAT(LIBC_NAMESPACE::remove(path_d), Succeeds());
+  ASSERT_THAT(LIBC_NAMESPACE::remove(path_a), Succeeds());
+  ASSERT_THAT(LIBC_NAMESPACE::remove(path_1), Succeeds());
+
+  free(path_d);
+  free(path_a);
+  free(path_1);
+
+  ASSERT_THAT(LIBC_NAMESPACE::rmdir(tmpl), Succeeds());
+  free(tmpl);
+}
 
 TEST_F(LlvmLibcScandirTest, TestBadDirname) {
   struct dirent **namelist;

>From 26b45786ce784e689ad2fc3b39b667c167cf4972 Mon Sep 17 00:00:00 2001
From: Vadim Kotov <kotov at google.com>
Date: Sat, 12 Sep 2026 18:07:20 -0700
Subject: [PATCH 14/30] Fix clang format errors

---
 .../llvm-libc-types/__scandir_compare_t.h     |  5 ++-
 .../llvm-libc-types/__scandir_filter_t.h      |  2 +-
 libc/src/dirent/linux/scandir.cpp             | 16 ++++---
 libc/test/src/dirent/scandir_test.cpp         | 44 ++++++++++---------
 4 files changed, 36 insertions(+), 31 deletions(-)

diff --git a/libc/include/llvm-libc-types/__scandir_compare_t.h b/libc/include/llvm-libc-types/__scandir_compare_t.h
index 0039188ee64709..615955d0c60c03 100644
--- a/libc/include/llvm-libc-types/__scandir_compare_t.h
+++ b/libc/include/llvm-libc-types/__scandir_compare_t.h
@@ -16,6 +16,7 @@
 
 #include "struct_dirent.h"
 
-typedef int (*__scandir_compare_t)(const struct dirent **, const struct dirent **);
+typedef int (*__scandir_compare_t)(const struct dirent **,
+                                   const struct dirent **);
 
-#endif  // LLVM_LIBC_TYPES___SCANDIR_COMPARE_T_H
+#endif // LLVM_LIBC_TYPES___SCANDIR_COMPARE_T_H
diff --git a/libc/include/llvm-libc-types/__scandir_filter_t.h b/libc/include/llvm-libc-types/__scandir_filter_t.h
index 204c950ee1e2c7..3f6aa99889f42b 100644
--- a/libc/include/llvm-libc-types/__scandir_filter_t.h
+++ b/libc/include/llvm-libc-types/__scandir_filter_t.h
@@ -18,4 +18,4 @@
 
 typedef int (*__scandir_filter_t)(const struct dirent *);
 
-#endif  // LLVM_LIBC_TYPES___SCANDIR_FILTER_H
+#endif // LLVM_LIBC_TYPES___SCANDIR_FILTER_H
diff --git a/libc/src/dirent/linux/scandir.cpp b/libc/src/dirent/linux/scandir.cpp
index 37fd06075b136c..d14b53255a1c4a 100644
--- a/libc/src/dirent/linux/scandir.cpp
+++ b/libc/src/dirent/linux/scandir.cpp
@@ -17,10 +17,10 @@
 #include "hdr/func/malloc.h"
 #include "hdr/types/size_t.h"
 #include "hdr/types/struct_dirent.h"
+#include "src/__support/CPP/vector.h"
 #include "src/__support/common.h"
-#include "src/__support/macros/config.h"
 #include "src/__support/libc_errno.h"
-#include "src/__support/CPP/vector.h"
+#include "src/__support/macros/config.h"
 #include "src/dirent/closedir.h"
 #include "src/dirent/opendir.h"
 #include "src/dirent/readdir.h"
@@ -40,7 +40,7 @@ LLVM_LIBC_FUNCTION(int, scandir,
   }
 
   int saved_errno = 0;
-  LIBC_NAMESPACE::cpp::vector<struct dirent*> entries;
+  LIBC_NAMESPACE::cpp::vector<struct dirent *> entries;
 
   while (true) {
     libc_errno = 0;
@@ -58,7 +58,8 @@ LLVM_LIBC_FUNCTION(int, scandir,
 
     // struct dirent contains an equivalent of flexible array memeber we
     // allocate with malloc and use d_reclen as size.
-    struct dirent *new_entry = static_cast<struct dirent*>(::malloc(entry->d_reclen));
+    struct dirent *new_entry =
+        static_cast<struct dirent *>(::malloc(entry->d_reclen));
     if (new_entry == nullptr) {
       saved_errno = ENOMEM;
     }
@@ -75,7 +76,7 @@ LLVM_LIBC_FUNCTION(int, scandir,
   // POSIX-defined error codes for scandir. So we ignore closedir's errno.
   LIBC_NAMESPACE::closedir(dir_fd);
 
-  struct dirent **result = static_cast<struct dirent**>(
+  struct dirent **result = static_cast<struct dirent **>(
       ::malloc(entries.size() * sizeof(struct dirent *)));
 
   if (result == nullptr) {
@@ -83,7 +84,7 @@ LLVM_LIBC_FUNCTION(int, scandir,
   }
 
   if (saved_errno != 0) {
-    for (struct dirent *entry: entries) {
+    for (struct dirent *entry : entries) {
       ::free(entry);
     }
     libc_errno = saved_errno;
@@ -96,7 +97,8 @@ LLVM_LIBC_FUNCTION(int, scandir,
       auto right = static_cast<const struct dirent **>(const_cast<void *>(b));
       return compare(left, right);
     };
-    internal::unstable_sort(entries.data(), entries.size(), sizeof(struct dirent*), cmp_fn);
+    internal::unstable_sort(entries.data(), entries.size(),
+                            sizeof(struct dirent *), cmp_fn);
   }
 
   for (size_t i = 0; i < entries.size(); ++i) {
diff --git a/libc/test/src/dirent/scandir_test.cpp b/libc/test/src/dirent/scandir_test.cpp
index 2acc0e3ed47c40..fa9b701f65a0e2 100644
--- a/libc/test/src/dirent/scandir_test.cpp
+++ b/libc/test/src/dirent/scandir_test.cpp
@@ -15,8 +15,8 @@
 #include "src/__support/OSUtil/path.h"
 #include "src/dirent/scandir.h"
 #include "src/stdio/asprintf.h"
-#include "src/stdio/fopen.h"
 #include "src/stdio/fclose.h"
+#include "src/stdio/fopen.h"
 #include "src/stdio/remove.h"
 #include "src/stdlib/mkdtemp.h"
 #include "src/string/strcoll.h"
@@ -37,12 +37,15 @@ constexpr int ENTRIES_MIN = 2;
 
 char *join_path(char *dir, const char *filename) {
   char *path = nullptr;
-  if (LIBC_NAMESPACE::asprintf(&path, "%s%c%s", dir, LIBC_NAMESPACE::path::SEPARATOR, filename) == -1) {
+  if (LIBC_NAMESPACE::asprintf(&path, "%s%c%s", dir,
+                               LIBC_NAMESPACE::path::SEPARATOR,
+                               filename) == -1) {
     return nullptr;
   }
   return path;
 }
-bool create_empty_file(char *path) { FILE *file = LIBC_NAMESPACE::fopen(path, "w");
+bool create_empty_file(char *path) {
+  FILE *file = LIBC_NAMESPACE::fopen(path, "w");
   if (file == nullptr) {
     return false;
   }
@@ -53,14 +56,11 @@ bool create_empty_file(char *path) { FILE *file = LIBC_NAMESPACE::fopen(path, "w
   return true;
 }
 
-
 int alphasort(const struct dirent **a, const struct dirent **b) {
   return LIBC_NAMESPACE::strcoll((*a)->d_name, (*b)->d_name);
 }
 
-int skip_hidden(const struct dirent *entry) {
-    return entry->d_name[0] != '.';
-}
+int skip_hidden(const struct dirent *entry) { return entry->d_name[0] != '.'; }
 
 TEST_F(LlvmLibcScandirTest, TestEmptyDir) {
   char *tmpl = LIBC_NAMESPACE::strdup(libc_make_test_file_path(TEMPLATE));
@@ -68,20 +68,19 @@ TEST_F(LlvmLibcScandirTest, TestEmptyDir) {
   ASSERT_THAT(LIBC_NAMESPACE::mkdtemp(tmpl), Succeeds(tmpl));
 
   struct dirent **namelist;
-  ASSERT_THAT(LIBC_NAMESPACE::scandir(tmpl, &namelist, nullptr, nullptr), Succeeds(ENTRIES_MIN));
+  ASSERT_THAT(LIBC_NAMESPACE::scandir(tmpl, &namelist, nullptr, nullptr),
+              Succeeds(ENTRIES_MIN));
   // Order of namelist is not guaranteed so we can't easily use ASSERT_STREQ
-  ASSERT_TRUE(
-      (LIBC_NAMESPACE::strncmp(namelist[0]->d_name, ".",  1) == 0 &&
-       LIBC_NAMESPACE::strncmp(namelist[1]->d_name, "..", 2) == 0) ||
-      (LIBC_NAMESPACE::strncmp(namelist[0]->d_name, "..", 2) == 0 &&
-       LIBC_NAMESPACE::strncmp(namelist[1]->d_name, ".",  1) == 0));
+  ASSERT_TRUE((LIBC_NAMESPACE::strncmp(namelist[0]->d_name, ".", 1) == 0 &&
+               LIBC_NAMESPACE::strncmp(namelist[1]->d_name, "..", 2) == 0) ||
+              (LIBC_NAMESPACE::strncmp(namelist[0]->d_name, "..", 2) == 0 &&
+               LIBC_NAMESPACE::strncmp(namelist[1]->d_name, ".", 1) == 0));
 
   // We also test that both orderings can't be true at the same time.
-  ASSERT_FALSE(
-      (LIBC_NAMESPACE::strncmp(namelist[0]->d_name, ".",  1) == 0 &&
-       LIBC_NAMESPACE::strncmp(namelist[1]->d_name, "..", 2) == 0) &&
-      (LIBC_NAMESPACE::strncmp(namelist[0]->d_name, "..", 2) == 0 &&
-       LIBC_NAMESPACE::strncmp(namelist[1]->d_name, ".",  1) == 0));
+  ASSERT_FALSE((LIBC_NAMESPACE::strncmp(namelist[0]->d_name, ".", 1) == 0 &&
+                LIBC_NAMESPACE::strncmp(namelist[1]->d_name, "..", 2) == 0) &&
+               (LIBC_NAMESPACE::strncmp(namelist[0]->d_name, "..", 2) == 0 &&
+                LIBC_NAMESPACE::strncmp(namelist[1]->d_name, ".", 1) == 0));
 
   ASSERT_THAT(LIBC_NAMESPACE::rmdir(tmpl), Succeeds());
   free(tmpl);
@@ -93,7 +92,8 @@ TEST_F(LlvmLibcScandirTest, TestDirFilter) {
   ASSERT_THAT(LIBC_NAMESPACE::mkdtemp(tmpl), Succeeds(tmpl));
 
   struct dirent **namelist;
-  ASSERT_THAT(LIBC_NAMESPACE::scandir(tmpl, &namelist, skip_hidden, nullptr), Succeeds(0));
+  ASSERT_THAT(LIBC_NAMESPACE::scandir(tmpl, &namelist, skip_hidden, nullptr),
+              Succeeds(0));
 
   ASSERT_THAT(LIBC_NAMESPACE::rmdir(tmpl), Succeeds());
   free(tmpl);
@@ -117,7 +117,8 @@ TEST_F(LlvmLibcScandirTest, TestDirSorted) {
   ASSERT_TRUE(create_empty_file(path_1));
 
   struct dirent **namelist;
-  ASSERT_THAT(LIBC_NAMESPACE::scandir(tmpl, &namelist, skip_hidden, alphasort), Succeeds(3));
+  ASSERT_THAT(LIBC_NAMESPACE::scandir(tmpl, &namelist, skip_hidden, alphasort),
+              Succeeds(3));
 
   ASSERT_STREQ(namelist[0]->d_name, "1");
   ASSERT_STREQ(namelist[1]->d_name, "a");
@@ -137,5 +138,6 @@ TEST_F(LlvmLibcScandirTest, TestDirSorted) {
 
 TEST_F(LlvmLibcScandirTest, TestBadDirname) {
   struct dirent **namelist;
-  ASSERT_THAT(LIBC_NAMESPACE::scandir("", &namelist, NULL, NULL), Fails(ENOENT, -1));
+  ASSERT_THAT(LIBC_NAMESPACE::scandir("", &namelist, NULL, NULL),
+              Fails(ENOENT, -1));
 }

>From 2987c928f92da61f60b44cdaa4336520de9a3576 Mon Sep 17 00:00:00 2001
From: Vadim Kotov <kotov at google.com>
Date: Wed, 16 Sep 2026 18:54:48 +0000
Subject: [PATCH 15/30] Add aarch64 and riscv entry points.

---
 libc/config/linux/aarch64/entrypoints.txt | 1 +
 libc/config/linux/riscv/entrypoints.txt   | 1 +
 2 files changed, 2 insertions(+)

diff --git a/libc/config/linux/aarch64/entrypoints.txt b/libc/config/linux/aarch64/entrypoints.txt
index f55ac4b16cfd26..4bf17c190aa02d 100644
--- a/libc/config/linux/aarch64/entrypoints.txt
+++ b/libc/config/linux/aarch64/entrypoints.txt
@@ -1104,6 +1104,7 @@ if(LLVM_LIBC_FULL_BUILD)
     libc.src.dirent.dirfd
     libc.src.dirent.opendir
     libc.src.dirent.readdir
+    libc.src.dirent.scandir
     libc.src.dirent.fdopendir
 
     # errno.h entrypoints
diff --git a/libc/config/linux/riscv/entrypoints.txt b/libc/config/linux/riscv/entrypoints.txt
index 921de1c649fb42..aa5ce91cb9edfc 100644
--- a/libc/config/linux/riscv/entrypoints.txt
+++ b/libc/config/linux/riscv/entrypoints.txt
@@ -1314,6 +1314,7 @@ if(LLVM_LIBC_FULL_BUILD)
     libc.src.dirent.dirfd
     libc.src.dirent.opendir
     libc.src.dirent.readdir
+    libc.src.dirent.scandir
     libc.src.dirent.fdopendir
 
     # pthread.h entrypoints

>From 1dc81331c12afa7e3f9b40d7a370b5eecfeca871 Mon Sep 17 00:00:00 2001
From: Vadim Kotov <kotov at google.com>
Date: Fri, 18 Sep 2026 00:18:38 +0000
Subject: [PATCH 16/30] Move scandir logic to dir.cpp

---
 libc/src/__support/File/CMakeLists.txt |  3 +
 libc/src/__support/File/dir.cpp        | 88 ++++++++++++++++++++++++++
 libc/src/__support/File/dir.h          |  6 ++
 libc/src/dirent/linux/CMakeLists.txt   | 11 +---
 libc/src/dirent/linux/scandir.cpp      | 87 ++-----------------------
 libc/test/src/dirent/scandir_test.cpp  |  1 +
 6 files changed, 108 insertions(+), 88 deletions(-)

diff --git a/libc/src/__support/File/CMakeLists.txt b/libc/src/__support/File/CMakeLists.txt
index f369fec9a915ce..3ec73483235abc 100644
--- a/libc/src/__support/File/CMakeLists.txt
+++ b/libc/src/__support/File/CMakeLists.txt
@@ -42,10 +42,13 @@ add_object_library(
     libc.src.__support.CPP.mutex
     libc.src.__support.CPP.new
     libc.src.__support.CPP.span
+		libc.src.__support.CPP.vector
     libc.src.__support.threads.mutex
     libc.src.__support.error_or
     libc.src.__support.macros.config
     libc.src.__support.alloc_checker
+    libc.src.stdlib.qsort_util
+		libc.src.string.memory_utils.inline_memcpy
     libc.hdr.errno_macros
     libc.hdr.types.struct_dirent
 )
diff --git a/libc/src/__support/File/dir.cpp b/libc/src/__support/File/dir.cpp
index eeb44c945b3f76..70dfbb43e5ff33 100644
--- a/libc/src/__support/File/dir.cpp
+++ b/libc/src/__support/File/dir.cpp
@@ -14,11 +14,18 @@
 #include "src/__support/File/dir.h"
 
 #include "hdr/errno_macros.h"
+#include "hdr/func/free.h"
+#include "hdr/func/malloc.h"
+#include "include/llvm-libc-types/__scandir_compare_t.h"
+#include "include/llvm-libc-types/__scandir_filter_t.h"
 #include "src/__support/CPP/mutex.h" // lock_guard
 #include "src/__support/CPP/new.h"
+#include "src/__support/CPP/vector.h"
 #include "src/__support/alloc-checker.h"
 #include "src/__support/error_or.h"
 #include "src/__support/macros/config.h"
+#include "src/stdlib/qsort_util.h"
+#include "src/string/memory_utils/inline_memcpy.h"
 
 namespace LIBC_NAMESPACE_DECL {
 
@@ -78,4 +85,85 @@ int Dir::close() {
   return 0;
 }
 
+ErrorOr<int> Dir::scan(const char *name, struct dirent ***namelist,
+                       __scandir_filter_t filter, __scandir_compare_t compare) {
+  auto res_open = Dir::open(name);
+  if (!res_open) {
+    return LIBC_NAMESPACE::Error(res_open.error());
+  }
+  Dir *dir = res_open.value();
+
+  cpp::vector<struct dirent *> entries;
+  int saved_errno = 0;
+
+  while (true) {
+    auto res_read = dir->read();
+    if (!res_read) {
+      saved_errno = res_read.error();
+      break;
+    }
+
+    struct dirent *entry = res_read.value();
+    if (entry == nullptr) {
+      break;
+    }
+
+    // Note, filter may modify errno
+    if (filter != nullptr && !filter(entry)) {
+      continue;
+    }
+
+    // struct dirent contains an equivalent of a flexible array memeber, so
+    // we can't use sizeof and d_reclen member is only available on Linux.
+    size_t reclen = platform_dir_reclen(entry);
+
+    struct dirent *new_entry = static_cast<struct dirent *>(::malloc(reclen));
+    if (new_entry == nullptr) {
+      saved_errno = ENOMEM;
+    }
+    inline_memcpy(new_entry, entry, reclen);
+
+    if (!entries.push_back(new_entry)) {
+      ::free(new_entry);
+      saved_errno = ENOMEM;
+      break;
+    }
+  }
+
+  // Closedir may modify errno and set it to, e.g. EBADF, which is not amongst
+  // POSIX-defined error codes for scandir.
+  dir->close();
+
+  struct dirent **result = static_cast<struct dirent **>(
+      ::malloc(entries.size() * sizeof(struct dirent *)));
+
+  if (result == nullptr) {
+    saved_errno = ENOMEM;
+  }
+
+  if (saved_errno != 0) {
+    for (struct dirent *entry : entries) {
+      ::free(entry);
+    }
+    return LIBC_NAMESPACE::Error(saved_errno);
+  }
+
+  if (compare != nullptr) {
+    auto cmp_fn = [compare](const void *a, const void *b) {
+      auto left = static_cast<const struct dirent **>(const_cast<void *>(a));
+      auto right = static_cast<const struct dirent **>(const_cast<void *>(b));
+      return compare(left, right);
+    };
+    internal::unstable_sort(entries.data(), entries.size(),
+                            sizeof(struct dirent *), cmp_fn);
+  }
+
+  for (size_t i = 0; i < entries.size(); ++i) {
+    result[i] = entries[i];
+  }
+
+  *namelist = result;
+  return static_cast<int>(entries.size());
+}
+
 } // namespace LIBC_NAMESPACE_DECL
diff --git a/libc/src/__support/File/dir.h b/libc/src/__support/File/dir.h
index 287e6b7e52f79f..912b0c760fa4ef 100644
--- a/libc/src/__support/File/dir.h
+++ b/libc/src/__support/File/dir.h
@@ -14,12 +14,15 @@
 #ifndef LLVM_LIBC_SRC___SUPPORT_FILE_DIR_H
 #define LLVM_LIBC_SRC___SUPPORT_FILE_DIR_H
 
+#include "include/llvm-libc-types/__scandir_compare_t.h"
+#include "include/llvm-libc-types/__scandir_filter_t.h"
 #include "src/__support/CPP/span.h"
 #include "src/__support/error_or.h"
 #include "src/__support/macros/config.h"
 #include "src/__support/threads/mutex.h"
 
 #include "hdr/types/struct_dirent.h"
+#include <dirent.h>
 
 namespace LIBC_NAMESPACE_DECL {
 
@@ -78,6 +81,9 @@ class Dir {
 public:
   static ErrorOr<Dir *> open(const char *path);
   static ErrorOr<Dir *> fdopen(int fd);
+  static ErrorOr<int> scan(const char *name, struct dirent ***namelist,
+                           __scandir_filter_t filter,
+                           __scandir_compare_t compare);
 
   ErrorOr<struct dirent *> read();
 
diff --git a/libc/src/dirent/linux/CMakeLists.txt b/libc/src/dirent/linux/CMakeLists.txt
index 71ec9be5766236..0071f71ac3aaca 100644
--- a/libc/src/dirent/linux/CMakeLists.txt
+++ b/libc/src/dirent/linux/CMakeLists.txt
@@ -5,16 +5,11 @@ add_entrypoint_object(
   HDRS
     ../scandir.h
   DEPENDS
-    libc.hdr.func.free
-    libc.hdr.func.malloc
-    libc.hdr.types.size_t
     libc.hdr.types.struct_dirent
     libc.src.__support.common
-    libc.src.__support.CPP.vector
+		libc.src.__support.File.dir
+		libc.src.__support.File.platform_dir
     libc.src.__support.macros.config
-    libc.src.dirent.closedir
-    libc.src.dirent.opendir
-    libc.src.dirent.readdir
     libc.src.errno.errno
-    libc.src.stdlib.qsort_util
+		libc.include.dirent
 )
diff --git a/libc/src/dirent/linux/scandir.cpp b/libc/src/dirent/linux/scandir.cpp
index d14b53255a1c4a..0596e585a6edcd 100644
--- a/libc/src/dirent/linux/scandir.cpp
+++ b/libc/src/dirent/linux/scandir.cpp
@@ -13,100 +13,27 @@
 
 #include "src/dirent/scandir.h"
 
-#include "hdr/func/free.h"
-#include "hdr/func/malloc.h"
-#include "hdr/types/size_t.h"
 #include "hdr/types/struct_dirent.h"
-#include "src/__support/CPP/vector.h"
+#include "src/__support/File/dir.h"
 #include "src/__support/common.h"
 #include "src/__support/libc_errno.h"
 #include "src/__support/macros/config.h"
-#include "src/dirent/closedir.h"
-#include "src/dirent/opendir.h"
-#include "src/dirent/readdir.h"
-#include "src/stdlib/malloc.h"
-#include "src/stdlib/qsort_util.h"
-#include "src/string/memcpy.h"
+
+#include <dirent.h>
 
 namespace LIBC_NAMESPACE_DECL {
 
 LLVM_LIBC_FUNCTION(int, scandir,
                    (const char *dir, struct dirent ***namelist,
                     __scandir_filter_t filter, __scandir_compare_t compare)) {
-  DIR *dir_fd = LIBC_NAMESPACE::opendir(dir);
-  if (dir_fd == nullptr) {
-    // errno set by opendir
-    return -1;
-  }
-
-  int saved_errno = 0;
-  LIBC_NAMESPACE::cpp::vector<struct dirent *> entries;
-
-  while (true) {
-    libc_errno = 0;
-    struct dirent *entry = LIBC_NAMESPACE::readdir(dir_fd);
-    if (entry == nullptr) {
-      // If the readdir call failed it set errno
-      saved_errno = libc_errno;
-      break;
-    }
-
-    // Note, filter may modify errno
-    if (filter != nullptr && !filter(entry)) {
-      continue;
-    }
-
-    // struct dirent contains an equivalent of flexible array memeber we
-    // allocate with malloc and use d_reclen as size.
-    struct dirent *new_entry =
-        static_cast<struct dirent *>(::malloc(entry->d_reclen));
-    if (new_entry == nullptr) {
-      saved_errno = ENOMEM;
-    }
-    LIBC_NAMESPACE::memcpy(new_entry, entry, entry->d_reclen);
-
-    if (!entries.push_back(new_entry)) {
-      free(new_entry);
-      saved_errno = ENOMEM;
-      break;
-    }
-  }
 
-  // Closedir may modify errno and set it to EBADF, which is not amongst
-  // POSIX-defined error codes for scandir. So we ignore closedir's errno.
-  LIBC_NAMESPACE::closedir(dir_fd);
-
-  struct dirent **result = static_cast<struct dirent **>(
-      ::malloc(entries.size() * sizeof(struct dirent *)));
-
-  if (result == nullptr) {
-    saved_errno = ENOMEM;
-  }
-
-  if (saved_errno != 0) {
-    for (struct dirent *entry : entries) {
-      ::free(entry);
-    }
-    libc_errno = saved_errno;
+  auto res = Dir::scan(dir, namelist, filter, compare);
+  if (!res) {
+    libc_errno = res.error();
     return -1;
   }
 
-  if (compare != nullptr) {
-    auto cmp_fn = [compare](const void *a, const void *b) {
-      auto left = static_cast<const struct dirent **>(const_cast<void *>(a));
-      auto right = static_cast<const struct dirent **>(const_cast<void *>(b));
-      return compare(left, right);
-    };
-    internal::unstable_sort(entries.data(), entries.size(),
-                            sizeof(struct dirent *), cmp_fn);
-  }
-
-  for (size_t i = 0; i < entries.size(); ++i) {
-    result[i] = entries[i];
-  }
-
-  *namelist = result;
-  return static_cast<int>(entries.size());
+  return res.value();
 }
 
 } // namespace LIBC_NAMESPACE_DECL
diff --git a/libc/test/src/dirent/scandir_test.cpp b/libc/test/src/dirent/scandir_test.cpp
index fa9b701f65a0e2..14afed2dd937ff 100644
--- a/libc/test/src/dirent/scandir_test.cpp
+++ b/libc/test/src/dirent/scandir_test.cpp
@@ -44,6 +44,7 @@ char *join_path(char *dir, const char *filename) {
   }
   return path;
 }
+
 bool create_empty_file(char *path) {
   FILE *file = LIBC_NAMESPACE::fopen(path, "w");
   if (file == nullptr) {

>From f4a4f50ed0c6988a7eeca1f268ab447e752549fe Mon Sep 17 00:00:00 2001
From: Vadim Kotov <kotov at google.com>
Date: Fri, 18 Sep 2026 00:24:15 +0000
Subject: [PATCH 17/30] Move scandir.cpp out of linux.

---
 libc/src/dirent/CMakeLists.txt          | 22 +++++++++++++---------
 libc/src/dirent/linux/CMakeLists.txt    | 15 ---------------
 libc/src/dirent/{linux => }/scandir.cpp |  0
 3 files changed, 13 insertions(+), 24 deletions(-)
 delete mode 100644 libc/src/dirent/linux/CMakeLists.txt
 rename libc/src/dirent/{linux => }/scandir.cpp (100%)

diff --git a/libc/src/dirent/CMakeLists.txt b/libc/src/dirent/CMakeLists.txt
index b5237333e29312..137c9e541bfb9d 100644
--- a/libc/src/dirent/CMakeLists.txt
+++ b/libc/src/dirent/CMakeLists.txt
@@ -1,8 +1,3 @@
-if(EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/${LIBC_TARGET_OS})
-  add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/${LIBC_TARGET_OS})
-endif()
-
-
 add_entrypoint_object(
   opendir
   SRCS
@@ -69,8 +64,17 @@ add_entrypoint_object(
 )
 
 add_entrypoint_object(
-  scandir
-  ALIAS
-  DEPENDS
-    .${LIBC_TARGET_OS}.scandir
+	scandir
+	SRCS
+	  scandir.cpp
+	HDRS
+	  scandir.h
+	DEPENDS
+    libc.hdr.types.struct_dirent
+    libc.src.__support.common
+		libc.src.__support.File.dir
+		libc.src.__support.File.platform_dir
+    libc.src.__support.macros.config
+    libc.src.errno.errno
+		libc.include.dirent
 )
diff --git a/libc/src/dirent/linux/CMakeLists.txt b/libc/src/dirent/linux/CMakeLists.txt
deleted file mode 100644
index 0071f71ac3aaca..00000000000000
--- a/libc/src/dirent/linux/CMakeLists.txt
+++ /dev/null
@@ -1,15 +0,0 @@
-add_entrypoint_object(
-  scandir
-  SRCS
-    scandir.cpp
-  HDRS
-    ../scandir.h
-  DEPENDS
-    libc.hdr.types.struct_dirent
-    libc.src.__support.common
-		libc.src.__support.File.dir
-		libc.src.__support.File.platform_dir
-    libc.src.__support.macros.config
-    libc.src.errno.errno
-		libc.include.dirent
-)
diff --git a/libc/src/dirent/linux/scandir.cpp b/libc/src/dirent/scandir.cpp
similarity index 100%
rename from libc/src/dirent/linux/scandir.cpp
rename to libc/src/dirent/scandir.cpp

>From cef16cdbefe7ae1ff86ff454ffb8e783e830850a Mon Sep 17 00:00:00 2001
From: Vadim Kotov <kotov at google.com>
Date: Sat, 19 Sep 2026 23:45:31 +0000
Subject: [PATCH 18/30] Extract scandir logic into a templated header and
 develop a simple example unit test utilizing it.

---
 libc/src/__support/File/CMakeLists.txt |  15 +++-
 libc/src/__support/File/dir.cpp        |  83 +-----------------
 libc/src/__support/File/scan_impl.h    | 115 +++++++++++++++++++++++++
 libc/test/src/dirent/CMakeLists.txt    |   2 +
 libc/test/src/dirent/scandir_test.cpp  |  40 +++++++++
 5 files changed, 171 insertions(+), 84 deletions(-)
 create mode 100644 libc/src/__support/File/scan_impl.h

diff --git a/libc/src/__support/File/CMakeLists.txt b/libc/src/__support/File/CMakeLists.txt
index 3ec73483235abc..133e7fe06c048c 100644
--- a/libc/src/__support/File/CMakeLists.txt
+++ b/libc/src/__support/File/CMakeLists.txt
@@ -32,6 +32,17 @@ add_object_library(
     libc.hdr.errno_macros
 )
 
+add_header_library(
+	scan_impl
+	HDRS
+    scan_impl.h
+	DEPENDS
+	  libc.src.__support.CPP.vector
+		libc.src.stdlib.qsort_util
+		libc.src.string.memory_utils.inline_memcpy
+    libc.hdr.types.struct_dirent
+)
+
 add_object_library(
   dir
   SRCS
@@ -39,16 +50,14 @@ add_object_library(
   HDRS
     dir.h
   DEPENDS
+	  .scan_impl
     libc.src.__support.CPP.mutex
     libc.src.__support.CPP.new
     libc.src.__support.CPP.span
-		libc.src.__support.CPP.vector
     libc.src.__support.threads.mutex
     libc.src.__support.error_or
     libc.src.__support.macros.config
     libc.src.__support.alloc_checker
-    libc.src.stdlib.qsort_util
-		libc.src.string.memory_utils.inline_memcpy
     libc.hdr.errno_macros
     libc.hdr.types.struct_dirent
 )
diff --git a/libc/src/__support/File/dir.cpp b/libc/src/__support/File/dir.cpp
index 70dfbb43e5ff33..6e6e84c2829a10 100644
--- a/libc/src/__support/File/dir.cpp
+++ b/libc/src/__support/File/dir.cpp
@@ -15,17 +15,14 @@
 
 #include "hdr/errno_macros.h"
 #include "hdr/func/free.h"
-#include "hdr/func/malloc.h"
 #include "include/llvm-libc-types/__scandir_compare_t.h"
 #include "include/llvm-libc-types/__scandir_filter_t.h"
 #include "src/__support/CPP/mutex.h" // lock_guard
 #include "src/__support/CPP/new.h"
-#include "src/__support/CPP/vector.h"
+#include "src/__support/File/scan_impl.h"
 #include "src/__support/alloc-checker.h"
 #include "src/__support/error_or.h"
 #include "src/__support/macros/config.h"
-#include "src/stdlib/qsort_util.h"
-#include "src/string/memory_utils/inline_memcpy.h"
 
 namespace LIBC_NAMESPACE_DECL {
 
@@ -87,83 +84,7 @@ int Dir::close() {
 
 ErrorOr<int> Dir::scan(const char *name, struct dirent ***namelist,
                        __scandir_filter_t filter, __scandir_compare_t compare) {
-  auto res_open = Dir::open(name);
-  if (!res_open) {
-    return LIBC_NAMESPACE::Error(res_open.error());
-  }
-  Dir *dir = res_open.value();
-
-  cpp::vector<struct dirent *> entries;
-  int saved_errno = 0;
-
-  while (true) {
-    auto res_read = dir->read();
-    if (!res_read) {
-      saved_errno = res_read.error();
-      break;
-    }
-
-    struct dirent *entry = res_read.value();
-    if (entry == nullptr) {
-      break;
-    }
-
-    // Note, filter may modify errno
-    if (filter != nullptr && !filter(entry)) {
-      continue;
-    }
-
-    // struct dirent contains an equivalent of a flexible array memeber, so
-    // we can't use sizeof and d_reclen member is only available on Linux.
-    size_t reclen = platform_dir_reclen(entry);
-
-    struct dirent *new_entry = static_cast<struct dirent *>(::malloc(reclen));
-    if (new_entry == nullptr) {
-      saved_errno = ENOMEM;
-    }
-    inline_memcpy(new_entry, entry, reclen);
-
-    if (!entries.push_back(new_entry)) {
-      ::free(new_entry);
-      saved_errno = ENOMEM;
-      break;
-    }
-  }
-
-  // Closedir may modify errno and set it to, e.g. EBADF, which is not amongst
-  // POSIX-defined error codes for scandir.
-  dir->close();
-
-  struct dirent **result = static_cast<struct dirent **>(
-      ::malloc(entries.size() * sizeof(struct dirent *)));
-
-  if (result == nullptr) {
-    saved_errno = ENOMEM;
-  }
-
-  if (saved_errno != 0) {
-    for (struct dirent *entry : entries) {
-      ::free(entry);
-    }
-    return LIBC_NAMESPACE::Error(saved_errno);
-  }
-
-  if (compare != nullptr) {
-    auto cmp_fn = [compare](const void *a, const void *b) {
-      auto left = static_cast<const struct dirent **>(const_cast<void *>(a));
-      auto right = static_cast<const struct dirent **>(const_cast<void *>(b));
-      return compare(left, right);
-    };
-    internal::unstable_sort(entries.data(), entries.size(),
-                            sizeof(struct dirent *), cmp_fn);
-  }
-
-  for (size_t i = 0; i < entries.size(); ++i) {
-    result[i] = entries[i];
-  }
-
-  *namelist = result;
-  return static_cast<int>(entries.size());
+  return internal::scan_impl<Dir>(name, namelist, filter, compare);
 }
 
 } // namespace LIBC_NAMESPACE_DECL
diff --git a/libc/src/__support/File/scan_impl.h b/libc/src/__support/File/scan_impl.h
new file mode 100644
index 00000000000000..3ffc4b1afb6a75
--- /dev/null
+++ b/libc/src/__support/File/scan_impl.h
@@ -0,0 +1,115 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+///
+/// \file
+/// Templated implementation of the scandir logic for dependency injection.
+///
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_LIBC_SRC___SUPPORT_FILE_SCAN_IMPL_H
+#define LLVM_LIBC_SRC___SUPPORT_FILE_SCAN_IMPL_H
+
+#include "hdr/func/free.h"
+#include "hdr/func/malloc.h"
+#include "hdr/types/struct_dirent.h"
+#include "include/llvm-libc-types/__scandir_compare_t.h"
+#include "include/llvm-libc-types/__scandir_filter_t.h"
+#include "src/__support/CPP/vector.h"
+#include "src/__support/File/dir.h"
+#include "src/stdlib/qsort_util.h"
+#include "src/string/memory_utils/inline_memcpy.h"
+
+namespace LIBC_NAMESPACE_DECL {
+namespace internal {
+
+template <typename DirType>
+ErrorOr<int> scan_impl(const char *name, struct dirent ***namelist,
+                       __scandir_filter_t filter, __scandir_compare_t compare) {
+  auto res_open = DirType::open(name);
+  if (!res_open) {
+    return LIBC_NAMESPACE::Error(res_open.error());
+  }
+  DirType *dir = res_open.value();
+
+  cpp::vector<struct dirent *> entries;
+  int saved_errno = 0;
+
+  while (true) {
+    auto res_read = dir->read();
+    if (!res_read) {
+      saved_errno = res_read.error();
+      break;
+    }
+
+    struct dirent *entry = res_read.value();
+    if (entry == nullptr) {
+      break;
+    }
+
+    // Note, filter may modify errno
+    if (filter != nullptr && !filter(entry)) {
+      continue;
+    }
+
+    // struct dirent contains an equivalent of a flexible array memeber, so
+    // we can't use sizeof and d_reclen member is only available on Linux.
+    size_t reclen = platform_dir_reclen(entry);
+
+    struct dirent *new_entry = static_cast<struct dirent *>(::malloc(reclen));
+    if (new_entry == nullptr) {
+      saved_errno = ENOMEM;
+    }
+    inline_memcpy(new_entry, entry, reclen);
+
+    if (!entries.push_back(new_entry)) {
+      ::free(new_entry);
+      saved_errno = ENOMEM;
+      break;
+    }
+  }
+
+  // Closedir may modify errno and set it to, e.g. EBADF, which is not amongst
+  // POSIX-defined error codes for scandir.
+  dir->close();
+
+  struct dirent **result = static_cast<struct dirent **>(
+      ::malloc(entries.size() * sizeof(struct dirent *)));
+
+  if (result == nullptr) {
+    saved_errno = ENOMEM;
+  }
+
+  if (saved_errno != 0) {
+    for (struct dirent *entry : entries) {
+      ::free(entry);
+    }
+    return LIBC_NAMESPACE::Error(saved_errno);
+  }
+
+  if (compare != nullptr) {
+    auto cmp_fn = [compare](const void *a, const void *b) {
+      auto left = static_cast<const struct dirent **>(const_cast<void *>(a));
+      auto right = static_cast<const struct dirent **>(const_cast<void *>(b));
+      return compare(left, right);
+    };
+    internal::unstable_sort(entries.data(), entries.size(),
+                            sizeof(struct dirent *), cmp_fn);
+  }
+
+  for (size_t i = 0; i < entries.size(); ++i) {
+    result[i] = entries[i];
+  }
+
+  *namelist = result;
+  return static_cast<int>(entries.size());
+}
+
+} // namespace internal
+} // namespace LIBC_NAMESPACE_DECL
+
+#endif // LLVM_LIBC_SRC___SUPPORT_FILE_SCAN_IMPL_H
diff --git a/libc/test/src/dirent/CMakeLists.txt b/libc/test/src/dirent/CMakeLists.txt
index 81bc4dd19bd5b9..b06ba5fe5d19b8 100644
--- a/libc/test/src/dirent/CMakeLists.txt
+++ b/libc/test/src/dirent/CMakeLists.txt
@@ -46,7 +46,9 @@ add_libc_test(
     scandir_test.cpp
   DEPENDS
     libc.hdr.types.struct_dirent
+		libc.src.__support.File.scan_impl
     libc.src.__support.OSUtil.path
+    libc.src.__support.error_or
     libc.src.dirent.scandir
     libc.src.stdio.asprintf
     libc.src.stdio.fopen
diff --git a/libc/test/src/dirent/scandir_test.cpp b/libc/test/src/dirent/scandir_test.cpp
index 14afed2dd937ff..3b17cd5e26e670 100644
--- a/libc/test/src/dirent/scandir_test.cpp
+++ b/libc/test/src/dirent/scandir_test.cpp
@@ -12,7 +12,9 @@
 //===----------------------------------------------------------------------===//
 
 #include "hdr/types/struct_dirent.h"
+#include "src/__support/File/scan_impl.h"
 #include "src/__support/OSUtil/path.h"
+#include "src/__support/error_or.h"
 #include "src/dirent/scandir.h"
 #include "src/stdio/asprintf.h"
 #include "src/stdio/fclose.h"
@@ -142,3 +144,41 @@ TEST_F(LlvmLibcScandirTest, TestBadDirname) {
   ASSERT_THAT(LIBC_NAMESPACE::scandir("", &namelist, NULL, NULL),
               Fails(ENOENT, -1));
 }
+
+namespace LIBC_NAMESPACE_DECL {
+
+struct MockDir {
+  static int read_call_count;
+
+  static LIBC_NAMESPACE::ErrorOr<MockDir *> open(const char *path) {
+    (void)path;
+    read_call_count = 0;
+    return new MockDir();
+  }
+
+  LIBC_NAMESPACE::ErrorOr<struct dirent *> read() {
+    read_call_count++;
+
+    if (read_call_count == 1) {
+      return LIBC_NAMESPACE::Error(EIO);
+    }
+    return nullptr;
+  }
+
+  int close() {
+    delete this;
+    return 0;
+  }
+};
+
+int MockDir::read_call_count = 0;
+
+} // namespace LIBC_NAMESPACE_DECL
+
+TEST_F(LlvmLibcScandirTest, ReadFailsWithEIO) {
+  struct dirent **namelist = nullptr;
+  auto res = LIBC_NAMESPACE::internal::scan_impl<LIBC_NAMESPACE::MockDir>(
+      "fake/path", &namelist, nullptr, nullptr);
+  ASSERT_FALSE(res.has_value());
+  EXPECT_EQ(res.error(), EIO);
+}

>From 5fa28341d1b66d714ee5a754466140dcd15eb804 Mon Sep 17 00:00:00 2001
From: Vadim Kotov <kotov at google.com>
Date: Sat, 19 Sep 2026 23:49:57 +0000
Subject: [PATCH 19/30] Fix tab/space formatting issues.

---
 libc/src/__support/File/CMakeLists.txt | 14 +++++++-------
 libc/src/dirent/CMakeLists.txt         |  6 +++---
 2 files changed, 10 insertions(+), 10 deletions(-)

diff --git a/libc/src/__support/File/CMakeLists.txt b/libc/src/__support/File/CMakeLists.txt
index 133e7fe06c048c..fa743fe3a124aa 100644
--- a/libc/src/__support/File/CMakeLists.txt
+++ b/libc/src/__support/File/CMakeLists.txt
@@ -33,13 +33,13 @@ add_object_library(
 )
 
 add_header_library(
-	scan_impl
-	HDRS
+  scan_impl
+  HDRS
     scan_impl.h
-	DEPENDS
-	  libc.src.__support.CPP.vector
-		libc.src.stdlib.qsort_util
-		libc.src.string.memory_utils.inline_memcpy
+  DEPENDS
+    libc.src.__support.CPP.vector
+    libc.src.stdlib.qsort_util
+    libc.src.string.memory_utils.inline_memcpy
     libc.hdr.types.struct_dirent
 )
 
@@ -50,7 +50,7 @@ add_object_library(
   HDRS
     dir.h
   DEPENDS
-	  .scan_impl
+    .scan_impl
     libc.src.__support.CPP.mutex
     libc.src.__support.CPP.new
     libc.src.__support.CPP.span
diff --git a/libc/src/dirent/CMakeLists.txt b/libc/src/dirent/CMakeLists.txt
index 137c9e541bfb9d..0079ccc182c7eb 100644
--- a/libc/src/dirent/CMakeLists.txt
+++ b/libc/src/dirent/CMakeLists.txt
@@ -72,9 +72,9 @@ add_entrypoint_object(
 	DEPENDS
     libc.hdr.types.struct_dirent
     libc.src.__support.common
-		libc.src.__support.File.dir
-		libc.src.__support.File.platform_dir
+    libc.src.__support.File.dir
+    libc.src.__support.File.platform_dir
     libc.src.__support.macros.config
     libc.src.errno.errno
-		libc.include.dirent
+    libc.include.dirent
 )

>From d5e231791569d2fb4407f8e35dec413ebcb41376 Mon Sep 17 00:00:00 2001
From: Vadim Kotov <kotov at google.com>
Date: Sat, 19 Sep 2026 23:51:27 +0000
Subject: [PATCH 20/30] Fix space/tab formatting issues.

---
 libc/test/src/dirent/CMakeLists.txt | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/libc/test/src/dirent/CMakeLists.txt b/libc/test/src/dirent/CMakeLists.txt
index b06ba5fe5d19b8..932bdfa29468bc 100644
--- a/libc/test/src/dirent/CMakeLists.txt
+++ b/libc/test/src/dirent/CMakeLists.txt
@@ -46,7 +46,7 @@ add_libc_test(
     scandir_test.cpp
   DEPENDS
     libc.hdr.types.struct_dirent
-		libc.src.__support.File.scan_impl
+    libc.src.__support.File.scan_impl
     libc.src.__support.OSUtil.path
     libc.src.__support.error_or
     libc.src.dirent.scandir

>From abef62e828ae644614efca18aa15528157c0b3bc Mon Sep 17 00:00:00 2001
From: Vadim Kotov <kotov at google.com>
Date: Sun, 20 Sep 2026 00:06:50 +0000
Subject: [PATCH 21/30] Create a mock Dir class that fails on read with a
 specified error.

---
 libc/test/src/dirent/scandir_test.cpp | 16 ++++++----------
 1 file changed, 6 insertions(+), 10 deletions(-)

diff --git a/libc/test/src/dirent/scandir_test.cpp b/libc/test/src/dirent/scandir_test.cpp
index 3b17cd5e26e670..01f9b4f89604f9 100644
--- a/libc/test/src/dirent/scandir_test.cpp
+++ b/libc/test/src/dirent/scandir_test.cpp
@@ -148,21 +148,16 @@ TEST_F(LlvmLibcScandirTest, TestBadDirname) {
 namespace LIBC_NAMESPACE_DECL {
 
 struct MockDir {
-  static int read_call_count;
+  static int read_errno_val;
 
   static LIBC_NAMESPACE::ErrorOr<MockDir *> open(const char *path) {
     (void)path;
-    read_call_count = 0;
     return new MockDir();
   }
 
   LIBC_NAMESPACE::ErrorOr<struct dirent *> read() {
-    read_call_count++;
 
-    if (read_call_count == 1) {
-      return LIBC_NAMESPACE::Error(EIO);
-    }
-    return nullptr;
+    return LIBC_NAMESPACE::Error(read_errno_val);
   }
 
   int close() {
@@ -171,14 +166,15 @@ struct MockDir {
   }
 };
 
-int MockDir::read_call_count = 0;
+int MockDir::read_errno_val = 0;
 
 } // namespace LIBC_NAMESPACE_DECL
 
-TEST_F(LlvmLibcScandirTest, ReadFailsWithEIO) {
+TEST_F(LlvmLibcScandirTest, ReadFailsWithENOENT) {
   struct dirent **namelist = nullptr;
+  LIBC_NAMESPACE::MockDir::read_errno_val = ENOENT;
   auto res = LIBC_NAMESPACE::internal::scan_impl<LIBC_NAMESPACE::MockDir>(
       "fake/path", &namelist, nullptr, nullptr);
   ASSERT_FALSE(res.has_value());
-  EXPECT_EQ(res.error(), EIO);
+  EXPECT_EQ(res.error(), ENOENT);
 }

>From 2c28ea0c7733a6e640ae634252468ccd281bba5a Mon Sep 17 00:00:00 2001
From: Vadim Kotov <kotov at google.com>
Date: Mon, 21 Sep 2026 21:27:00 +0000
Subject: [PATCH 22/30] Extract scan_impl test into its separate file in
 __support/File.

---
 libc/src/__support/File/scan_impl.h           |  23 ++--
 libc/test/src/__support/File/CMakeLists.txt   |  14 +++
 .../src/__support/File/scan_impl_test.cpp     |  51 +++++++++
 libc/test/src/dirent/scandir_test.cpp         | 107 ++++++++++--------
 4 files changed, 138 insertions(+), 57 deletions(-)
 create mode 100644 libc/test/src/__support/File/scan_impl_test.cpp

diff --git a/libc/src/__support/File/scan_impl.h b/libc/src/__support/File/scan_impl.h
index 3ffc4b1afb6a75..8777aa7c72a66e 100644
--- a/libc/src/__support/File/scan_impl.h
+++ b/libc/src/__support/File/scan_impl.h
@@ -37,6 +37,13 @@ ErrorOr<int> scan_impl(const char *name, struct dirent ***namelist,
   DirType *dir = res_open.value();
 
   cpp::vector<struct dirent *> entries;
+
+  auto free_entries = [&entries]() {
+    for (struct dirent *entry : entries) {
+      ::free(entry);
+    }
+  };
+
   int saved_errno = 0;
 
   while (true) {
@@ -63,6 +70,7 @@ ErrorOr<int> scan_impl(const char *name, struct dirent ***namelist,
     struct dirent *new_entry = static_cast<struct dirent *>(::malloc(reclen));
     if (new_entry == nullptr) {
       saved_errno = ENOMEM;
+      break;
     }
     inline_memcpy(new_entry, entry, reclen);
 
@@ -77,18 +85,17 @@ ErrorOr<int> scan_impl(const char *name, struct dirent ***namelist,
   // POSIX-defined error codes for scandir.
   dir->close();
 
+  if (saved_errno != 0) {
+    free_entries();
+    return LIBC_NAMESPACE::Error(saved_errno);
+  }
+
   struct dirent **result = static_cast<struct dirent **>(
       ::malloc(entries.size() * sizeof(struct dirent *)));
 
   if (result == nullptr) {
-    saved_errno = ENOMEM;
-  }
-
-  if (saved_errno != 0) {
-    for (struct dirent *entry : entries) {
-      ::free(entry);
-    }
-    return LIBC_NAMESPACE::Error(saved_errno);
+    free_entries();
+    return LIBC_NAMESPACE::Error(ENOMEM);
   }
 
   if (compare != nullptr) {
diff --git a/libc/test/src/__support/File/CMakeLists.txt b/libc/test/src/__support/File/CMakeLists.txt
index bd5fe622fede01..778691a00c10cf 100644
--- a/libc/test/src/__support/File/CMakeLists.txt
+++ b/libc/test/src/__support/File/CMakeLists.txt
@@ -39,11 +39,25 @@ add_libc_test(
   SRCS
     platform_file_test.cpp
   DEPENDS
+    libc.src.__support.error_or
     libc.src.__support.File.file
     libc.src.__support.File.platform_file
     libc.include.stdio
 )
 
+add_libc_test(
+  scan_impl_test
+  SUITE
+    libc-support-tests
+  SRCS
+    scan_impl_test.cpp
+  DEPENDS
+    libc.hdr.types.struct_dirent
+    libc.src.__support.File.scan_impl
+    libc.src.__support.File.dir
+    libc.src.__support.File.platform_dir
+)
+
 set(platform_stream_targets "platform_stdout;platform_stdin;platform_stderr")
 foreach(target IN LISTS platform_file_targets)
   if(TARGET libc.src.__support.File.${target})
diff --git a/libc/test/src/__support/File/scan_impl_test.cpp b/libc/test/src/__support/File/scan_impl_test.cpp
new file mode 100644
index 00000000000000..6cff75f8fa6960
--- /dev/null
+++ b/libc/test/src/__support/File/scan_impl_test.cpp
@@ -0,0 +1,51 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+///
+/// \file
+/// Unittests templated implementation of the scandir logic.
+///
+//===----------------------------------------------------------------------===//
+
+#include "hdr/types/struct_dirent.h"
+#include "src/__support/error_or.h"
+#include "src/__support/File/scan_impl.h"
+#include "test/UnitTest/Test.h"
+
+namespace LIBC_NAMESPACE_DECL {
+
+struct MockDir {
+  static int read_errno_val;
+
+  static LIBC_NAMESPACE::ErrorOr<MockDir *> open(const char *path) {
+    (void)path;
+    return new MockDir();
+  }
+
+  LIBC_NAMESPACE::ErrorOr<struct dirent *> read() {
+
+    return LIBC_NAMESPACE::Error(read_errno_val);
+  }
+
+  int close() {
+    delete this;
+    return 0;
+  }
+};
+
+int MockDir::read_errno_val = 0;
+
+} // namespace LIBC_NAMESPACE_DECL
+
+TEST(LlvmLibcScanImplTest, ReadFailsWithENOENT) {
+  struct dirent **namelist = nullptr;
+  LIBC_NAMESPACE::MockDir::read_errno_val = ENOENT;
+  auto res = LIBC_NAMESPACE::internal::scan_impl<LIBC_NAMESPACE::MockDir>(
+      "fake/path", &namelist, nullptr, nullptr);
+  ASSERT_FALSE(res.has_value());
+  EXPECT_EQ(res.error(), ENOENT);
+}
diff --git a/libc/test/src/dirent/scandir_test.cpp b/libc/test/src/dirent/scandir_test.cpp
index 01f9b4f89604f9..faa776e7e044b1 100644
--- a/libc/test/src/dirent/scandir_test.cpp
+++ b/libc/test/src/dirent/scandir_test.cpp
@@ -12,9 +12,7 @@
 //===----------------------------------------------------------------------===//
 
 #include "hdr/types/struct_dirent.h"
-#include "src/__support/File/scan_impl.h"
 #include "src/__support/OSUtil/path.h"
-#include "src/__support/error_or.h"
 #include "src/dirent/scandir.h"
 #include "src/stdio/asprintf.h"
 #include "src/stdio/fclose.h"
@@ -59,19 +57,49 @@ bool create_empty_file(char *path) {
   return true;
 }
 
+char *create_temp_dir() {
+  char *tmpl = LIBC_NAMESPACE::strdup(libc_make_test_file_path(TEMPLATE));
+  if (tmpl == nullptr) {
+    return nullptr;
+  }
+  return LIBC_NAMESPACE::mkdtemp(tmpl);
+}
+
+bool remove_temp_dir(char *dirpath) {
+  if (LIBC_NAMESPACE::rmdir(dirpath) == -1) {
+    return false;
+  }
+  free(dirpath);
+  return true;
+}
+
 int alphasort(const struct dirent **a, const struct dirent **b) {
   return LIBC_NAMESPACE::strcoll((*a)->d_name, (*b)->d_name);
 }
 
+int omegasort(const struct dirent **a, const struct dirent **b) {
+  return -LIBC_NAMESPACE::strcoll((*a)->d_name, (*b)->d_name);
+}
+
 int skip_hidden(const struct dirent *entry) { return entry->d_name[0] != '.'; }
 
+void free_namelist(struct dirent **namelist, int size) {
+  if (namelist == nullptr) {
+    return;
+  }
+
+  for (int i = 0; i < size; ++i) {
+    ::free(namelist[i]);
+  }
+  ::free(namelist);
+}
+
 TEST_F(LlvmLibcScandirTest, TestEmptyDir) {
-  char *tmpl = LIBC_NAMESPACE::strdup(libc_make_test_file_path(TEMPLATE));
-  ASSERT_NE(tmpl, nullptr);
-  ASSERT_THAT(LIBC_NAMESPACE::mkdtemp(tmpl), Succeeds(tmpl));
+  char *dirpath = create_temp_dir();
+  ASSERT_NE(dirpath, nullptr);
 
   struct dirent **namelist;
-  ASSERT_THAT(LIBC_NAMESPACE::scandir(tmpl, &namelist, nullptr, nullptr),
+  ASSERT_THAT(LIBC_NAMESPACE::scandir(dirpath, &namelist, nullptr, nullptr),
               Succeeds(ENTRIES_MIN));
   // Order of namelist is not guaranteed so we can't easily use ASSERT_STREQ
   ASSERT_TRUE((LIBC_NAMESPACE::strncmp(namelist[0]->d_name, ".", 1) == 0 &&
@@ -85,21 +113,20 @@ TEST_F(LlvmLibcScandirTest, TestEmptyDir) {
                (LIBC_NAMESPACE::strncmp(namelist[0]->d_name, "..", 2) == 0 &&
                 LIBC_NAMESPACE::strncmp(namelist[1]->d_name, ".", 1) == 0));
 
-  ASSERT_THAT(LIBC_NAMESPACE::rmdir(tmpl), Succeeds());
-  free(tmpl);
+  free_namelist(namelist, ENTRIES_MIN);
+  ASSERT_TRUE(remove_temp_dir(dirpath));
 }
 
 TEST_F(LlvmLibcScandirTest, TestDirFilter) {
-  char *tmpl = LIBC_NAMESPACE::strdup(libc_make_test_file_path(TEMPLATE));
-  ASSERT_NE(tmpl, nullptr);
-  ASSERT_THAT(LIBC_NAMESPACE::mkdtemp(tmpl), Succeeds(tmpl));
+  char *dirpath = create_temp_dir();
+  ASSERT_NE(dirpath, nullptr);
 
   struct dirent **namelist;
-  ASSERT_THAT(LIBC_NAMESPACE::scandir(tmpl, &namelist, skip_hidden, nullptr),
+  ASSERT_THAT(LIBC_NAMESPACE::scandir(dirpath, &namelist, skip_hidden, nullptr),
               Succeeds(0));
 
-  ASSERT_THAT(LIBC_NAMESPACE::rmdir(tmpl), Succeeds());
-  free(tmpl);
+  free_namelist(namelist, 0);
+  ASSERT_TRUE(remove_temp_dir(dirpath));
 }
 
 TEST_F(LlvmLibcScandirTest, TestDirSorted) {
@@ -119,7 +146,7 @@ TEST_F(LlvmLibcScandirTest, TestDirSorted) {
   ASSERT_TRUE(path_1 != nullptr);
   ASSERT_TRUE(create_empty_file(path_1));
 
-  struct dirent **namelist;
+  struct dirent **namelist = nullptr;
   ASSERT_THAT(LIBC_NAMESPACE::scandir(tmpl, &namelist, skip_hidden, alphasort),
               Succeeds(3));
 
@@ -127,10 +154,23 @@ TEST_F(LlvmLibcScandirTest, TestDirSorted) {
   ASSERT_STREQ(namelist[1]->d_name, "a");
   ASSERT_STREQ(namelist[2]->d_name, "d");
 
+  free_namelist(namelist, 3);
+
+  // Reverse alphanumeric sort in case the above sorting test passed on chance.
+  namelist = nullptr;
+  ASSERT_THAT(LIBC_NAMESPACE::scandir(tmpl, &namelist, skip_hidden, omegasort),
+              Succeeds(3));
+
+  ASSERT_STREQ(namelist[0]->d_name, "d");
+  ASSERT_STREQ(namelist[1]->d_name, "a");
+  ASSERT_STREQ(namelist[2]->d_name, "1");
+
   ASSERT_THAT(LIBC_NAMESPACE::remove(path_d), Succeeds());
   ASSERT_THAT(LIBC_NAMESPACE::remove(path_a), Succeeds());
   ASSERT_THAT(LIBC_NAMESPACE::remove(path_1), Succeeds());
 
+  free_namelist(namelist, 3);
+
   free(path_d);
   free(path_a);
   free(path_1);
@@ -139,42 +179,11 @@ TEST_F(LlvmLibcScandirTest, TestDirSorted) {
   free(tmpl);
 }
 
+// While this test only checks for one type of ERROR, it really tests
+// the error propagation from Dir::open. And as such we don't really
+// have to test for every error inherited from Dir::open.
 TEST_F(LlvmLibcScandirTest, TestBadDirname) {
   struct dirent **namelist;
   ASSERT_THAT(LIBC_NAMESPACE::scandir("", &namelist, NULL, NULL),
               Fails(ENOENT, -1));
 }
-
-namespace LIBC_NAMESPACE_DECL {
-
-struct MockDir {
-  static int read_errno_val;
-
-  static LIBC_NAMESPACE::ErrorOr<MockDir *> open(const char *path) {
-    (void)path;
-    return new MockDir();
-  }
-
-  LIBC_NAMESPACE::ErrorOr<struct dirent *> read() {
-
-    return LIBC_NAMESPACE::Error(read_errno_val);
-  }
-
-  int close() {
-    delete this;
-    return 0;
-  }
-};
-
-int MockDir::read_errno_val = 0;
-
-} // namespace LIBC_NAMESPACE_DECL
-
-TEST_F(LlvmLibcScandirTest, ReadFailsWithENOENT) {
-  struct dirent **namelist = nullptr;
-  LIBC_NAMESPACE::MockDir::read_errno_val = ENOENT;
-  auto res = LIBC_NAMESPACE::internal::scan_impl<LIBC_NAMESPACE::MockDir>(
-      "fake/path", &namelist, nullptr, nullptr);
-  ASSERT_FALSE(res.has_value());
-  EXPECT_EQ(res.error(), ENOENT);
-}

>From c88e27c808ef863c377d75c7587804bbaccf67e8 Mon Sep 17 00:00:00 2001
From: Vadim Kotov <kotov at google.com>
Date: Mon, 21 Sep 2026 21:49:27 +0000
Subject: [PATCH 23/30] Refactor and update scandir_test and scan_impl_test.

---
 .../src/__support/File/scan_impl_test.cpp     | 33 ++++++++++--
 libc/test/src/dirent/scandir_test.cpp         | 54 +++++++++----------
 2 files changed, 54 insertions(+), 33 deletions(-)

diff --git a/libc/test/src/__support/File/scan_impl_test.cpp b/libc/test/src/__support/File/scan_impl_test.cpp
index 6cff75f8fa6960..29df4c7162a3ea 100644
--- a/libc/test/src/__support/File/scan_impl_test.cpp
+++ b/libc/test/src/__support/File/scan_impl_test.cpp
@@ -12,14 +12,17 @@
 //===----------------------------------------------------------------------===//
 
 #include "hdr/types/struct_dirent.h"
-#include "src/__support/error_or.h"
 #include "src/__support/File/scan_impl.h"
+#include "src/__support/error_or.h"
 #include "test/UnitTest/Test.h"
 
 namespace LIBC_NAMESPACE_DECL {
 
 struct MockDir {
   static int read_errno_val;
+  static int read_fails_at; // how many successful reads until it fails.
+
+  static struct dirent dummy_entry;
 
   static LIBC_NAMESPACE::ErrorOr<MockDir *> open(const char *path) {
     (void)path;
@@ -27,8 +30,15 @@ struct MockDir {
   }
 
   LIBC_NAMESPACE::ErrorOr<struct dirent *> read() {
+    read_fails_at--;
+    if (read_fails_at <= 0) {
+      return LIBC_NAMESPACE::Error(read_errno_val);
+    }
 
-    return LIBC_NAMESPACE::Error(read_errno_val);
+    dummy_entry.d_reclen = sizeof(struct dirent);
+    dummy_entry.d_name[0] = 'a';
+    dummy_entry.d_name[1] = '\0';
+    return &dummy_entry;
   }
 
   int close() {
@@ -38,12 +48,29 @@ struct MockDir {
 };
 
 int MockDir::read_errno_val = 0;
+int MockDir::read_fails_at = 1;
+struct dirent MockDir::dummy_entry = {};
 
 } // namespace LIBC_NAMESPACE_DECL
 
-TEST(LlvmLibcScanImplTest, ReadFailsWithENOENT) {
+TEST(LlvmLibcScanImplTest, ReadFailsAtStart) {
+
   struct dirent **namelist = nullptr;
   LIBC_NAMESPACE::MockDir::read_errno_val = ENOENT;
+  LIBC_NAMESPACE::MockDir::read_fails_at = 1;
+
+  auto res = LIBC_NAMESPACE::internal::scan_impl<LIBC_NAMESPACE::MockDir>(
+      "fake/path", &namelist, nullptr, nullptr);
+  ASSERT_FALSE(res.has_value());
+  EXPECT_EQ(res.error(), ENOENT);
+}
+
+TEST(LlvmLibcScanImplTest, ReadFailsMidway) {
+
+  struct dirent **namelist = nullptr;
+  LIBC_NAMESPACE::MockDir::read_errno_val = ENOENT;
+  LIBC_NAMESPACE::MockDir::read_fails_at = 3;
+
   auto res = LIBC_NAMESPACE::internal::scan_impl<LIBC_NAMESPACE::MockDir>(
       "fake/path", &namelist, nullptr, nullptr);
   ASSERT_FALSE(res.has_value());
diff --git a/libc/test/src/dirent/scandir_test.cpp b/libc/test/src/dirent/scandir_test.cpp
index faa776e7e044b1..4520d4540ef2e8 100644
--- a/libc/test/src/dirent/scandir_test.cpp
+++ b/libc/test/src/dirent/scandir_test.cpp
@@ -130,53 +130,47 @@ TEST_F(LlvmLibcScandirTest, TestDirFilter) {
 }
 
 TEST_F(LlvmLibcScandirTest, TestDirSorted) {
-  char *tmpl = LIBC_NAMESPACE::strdup(libc_make_test_file_path(TEMPLATE));
-  ASSERT_NE(tmpl, nullptr);
-  ASSERT_THAT(LIBC_NAMESPACE::mkdtemp(tmpl), Succeeds(tmpl));
-
-  char *path_d = join_path(tmpl, "d");
-  ASSERT_TRUE(path_d != nullptr);
-  ASSERT_TRUE(create_empty_file(path_d));
+  char *dirpath = create_temp_dir();
+  ASSERT_NE(dirpath, nullptr);
 
-  char *path_a = join_path(tmpl, "a");
-  ASSERT_TRUE(path_a != nullptr);
-  ASSERT_TRUE(create_empty_file(path_a));
+  const char *files_to_create[] = {"d", "a", "1"};
+  constexpr size_t NUM_FILES =
+      sizeof(files_to_create) / sizeof(files_to_create[0]);
+  char *filepaths[NUM_FILES];
 
-  char *path_1 = join_path(tmpl, "1");
-  ASSERT_TRUE(path_1 != nullptr);
-  ASSERT_TRUE(create_empty_file(path_1));
+  for (size_t i = 0; i < NUM_FILES; ++i) {
+    filepaths[i] = join_path(dirpath, files_to_create[i]);
+    ASSERT_NE(filepaths[i], nullptr);
+    ASSERT_TRUE(create_empty_file(filepaths[i]));
+  }
 
   struct dirent **namelist = nullptr;
-  ASSERT_THAT(LIBC_NAMESPACE::scandir(tmpl, &namelist, skip_hidden, alphasort),
-              Succeeds(3));
+  ASSERT_THAT(
+      LIBC_NAMESPACE::scandir(dirpath, &namelist, skip_hidden, alphasort),
+      Succeeds(3));
 
   ASSERT_STREQ(namelist[0]->d_name, "1");
   ASSERT_STREQ(namelist[1]->d_name, "a");
   ASSERT_STREQ(namelist[2]->d_name, "d");
-
-  free_namelist(namelist, 3);
+  free_namelist(namelist, NUM_FILES);
 
   // Reverse alphanumeric sort in case the above sorting test passed on chance.
   namelist = nullptr;
-  ASSERT_THAT(LIBC_NAMESPACE::scandir(tmpl, &namelist, skip_hidden, omegasort),
-              Succeeds(3));
+  ASSERT_THAT(
+      LIBC_NAMESPACE::scandir(dirpath, &namelist, skip_hidden, omegasort),
+      Succeeds(3));
 
   ASSERT_STREQ(namelist[0]->d_name, "d");
   ASSERT_STREQ(namelist[1]->d_name, "a");
   ASSERT_STREQ(namelist[2]->d_name, "1");
+  free_namelist(namelist, NUM_FILES);
 
-  ASSERT_THAT(LIBC_NAMESPACE::remove(path_d), Succeeds());
-  ASSERT_THAT(LIBC_NAMESPACE::remove(path_a), Succeeds());
-  ASSERT_THAT(LIBC_NAMESPACE::remove(path_1), Succeeds());
-
-  free_namelist(namelist, 3);
-
-  free(path_d);
-  free(path_a);
-  free(path_1);
+  for (size_t i = 0; i < NUM_FILES; ++i) {
+    ASSERT_THAT(LIBC_NAMESPACE::remove(filepaths[i]), Succeeds());
+    ::free(filepaths[i]);
+  }
 
-  ASSERT_THAT(LIBC_NAMESPACE::rmdir(tmpl), Succeeds());
-  free(tmpl);
+  ASSERT_TRUE(remove_temp_dir(dirpath));
 }
 
 // While this test only checks for one type of ERROR, it really tests

>From 1d742ebeb733c7f6b99fdca1e901cbad482327e5 Mon Sep 17 00:00:00 2001
From: Vadim Kotov <kotov at google.com>
Date: Mon, 21 Sep 2026 22:02:41 +0000
Subject: [PATCH 24/30] Add partial filter teset to scandir_test.

---
 libc/test/src/dirent/scandir_test.cpp | 33 ++++++++++++++++++++++++++-
 1 file changed, 32 insertions(+), 1 deletion(-)

diff --git a/libc/test/src/dirent/scandir_test.cpp b/libc/test/src/dirent/scandir_test.cpp
index 4520d4540ef2e8..9a866f0ecccaaa 100644
--- a/libc/test/src/dirent/scandir_test.cpp
+++ b/libc/test/src/dirent/scandir_test.cpp
@@ -83,6 +83,8 @@ int omegasort(const struct dirent **a, const struct dirent **b) {
 
 int skip_hidden(const struct dirent *entry) { return entry->d_name[0] != '.'; }
 
+int skip_as(const struct dirent *entry) { return entry->d_name[0] != 'a'; }
+
 void free_namelist(struct dirent **namelist, int size) {
   if (namelist == nullptr) {
     return;
@@ -117,7 +119,7 @@ TEST_F(LlvmLibcScandirTest, TestEmptyDir) {
   ASSERT_TRUE(remove_temp_dir(dirpath));
 }
 
-TEST_F(LlvmLibcScandirTest, TestDirFilter) {
+TEST_F(LlvmLibcScandirTest, TestFilter) {
   char *dirpath = create_temp_dir();
   ASSERT_NE(dirpath, nullptr);
 
@@ -129,6 +131,35 @@ TEST_F(LlvmLibcScandirTest, TestDirFilter) {
   ASSERT_TRUE(remove_temp_dir(dirpath));
 }
 
+TEST_F(LlvmLibcScandirTest, TestPartialFilter) {
+  char *dirpath = create_temp_dir();
+  ASSERT_NE(dirpath, nullptr);
+
+  const char *files_to_create[] = {"a0", "a1", "b0"};
+  constexpr size_t NUM_FILES =
+      sizeof(files_to_create) / sizeof(files_to_create[0]);
+  char *filepaths[NUM_FILES];
+
+  for (size_t i = 0; i < NUM_FILES; ++i) {
+    filepaths[i] = join_path(dirpath, files_to_create[i]);
+    ASSERT_NE(filepaths[i], nullptr);
+    ASSERT_TRUE(create_empty_file(filepaths[i]));
+  }
+
+  struct dirent **namelist;
+  ASSERT_THAT(LIBC_NAMESPACE::scandir(dirpath, &namelist, skip_as, nullptr),
+              Succeeds(ENTRIES_MIN + 1));
+
+  free_namelist(namelist, NUM_FILES);
+
+  for (size_t i = 0; i < NUM_FILES; ++i) {
+    ASSERT_THAT(LIBC_NAMESPACE::remove(filepaths[i]), Succeeds());
+    ::free(filepaths[i]);
+  }
+
+  ASSERT_TRUE(remove_temp_dir(dirpath));
+}
+
 TEST_F(LlvmLibcScandirTest, TestDirSorted) {
   char *dirpath = create_temp_dir();
   ASSERT_NE(dirpath, nullptr);

>From ee84f128a0edc5f37491efe7aa6bbf32330b94a9 Mon Sep 17 00:00:00 2001
From: Vadim Kotov <kotov at google.com>
Date: Mon, 21 Sep 2026 22:25:48 +0000
Subject: [PATCH 25/30] Add guards for malloc(0) and entries.empty() when using
 the comparator.

---
 libc/src/__support/File/scan_impl.h | 12 ++++++++++--
 1 file changed, 10 insertions(+), 2 deletions(-)

diff --git a/libc/src/__support/File/scan_impl.h b/libc/src/__support/File/scan_impl.h
index 8777aa7c72a66e..bef08225aabe64 100644
--- a/libc/src/__support/File/scan_impl.h
+++ b/libc/src/__support/File/scan_impl.h
@@ -90,15 +90,23 @@ ErrorOr<int> scan_impl(const char *name, struct dirent ***namelist,
     return LIBC_NAMESPACE::Error(saved_errno);
   }
 
+  size_t alloc_size = entries.size() * sizeof(struct dirent *); 
+  // The filter may have filtered out all entries. We'd like to avoid
+  // malloc(0) in this instance as its exact semantics might be
+  // implementation-dependent.
+  if (alloc_size == 0) {
+    alloc_size = sizeof(struct dirent *);
+  }
+
   struct dirent **result = static_cast<struct dirent **>(
-      ::malloc(entries.size() * sizeof(struct dirent *)));
+      ::malloc(alloc_size));
 
   if (result == nullptr) {
     free_entries();
     return LIBC_NAMESPACE::Error(ENOMEM);
   }
 
-  if (compare != nullptr) {
+  if (compare != nullptr && !entries.empty()) {
     auto cmp_fn = [compare](const void *a, const void *b) {
       auto left = static_cast<const struct dirent **>(const_cast<void *>(a));
       auto right = static_cast<const struct dirent **>(const_cast<void *>(b));

>From 2bdae6fee5e101d30bde58e292f704f1fd0e4e93 Mon Sep 17 00:00:00 2001
From: Vadim Kotov <kotov at google.com>
Date: Mon, 21 Sep 2026 22:52:58 +0000
Subject: [PATCH 26/30] Fix clang formatting

---
 libc/src/__support/File/scan_impl.h | 5 ++---
 1 file changed, 2 insertions(+), 3 deletions(-)

diff --git a/libc/src/__support/File/scan_impl.h b/libc/src/__support/File/scan_impl.h
index bef08225aabe64..fea69ee36ec08b 100644
--- a/libc/src/__support/File/scan_impl.h
+++ b/libc/src/__support/File/scan_impl.h
@@ -90,7 +90,7 @@ ErrorOr<int> scan_impl(const char *name, struct dirent ***namelist,
     return LIBC_NAMESPACE::Error(saved_errno);
   }
 
-  size_t alloc_size = entries.size() * sizeof(struct dirent *); 
+  size_t alloc_size = entries.size() * sizeof(struct dirent *);
   // The filter may have filtered out all entries. We'd like to avoid
   // malloc(0) in this instance as its exact semantics might be
   // implementation-dependent.
@@ -98,8 +98,7 @@ ErrorOr<int> scan_impl(const char *name, struct dirent ***namelist,
     alloc_size = sizeof(struct dirent *);
   }
 
-  struct dirent **result = static_cast<struct dirent **>(
-      ::malloc(alloc_size));
+  struct dirent **result = static_cast<struct dirent **>(::malloc(alloc_size));
 
   if (result == nullptr) {
     free_entries();

>From a508081f1371f176c04cac7944f052a815a3f4ad Mon Sep 17 00:00:00 2001
From: Vadim Kotov <kotov at google.com>
Date: Thu, 24 Sep 2026 21:58:26 +0000
Subject: [PATCH 27/30] Remove dependency on CPP::vector, fix bugs and do
 dependecy clean up.

---
 libc/src/__support/File/CMakeLists.txt        |  4 +-
 libc/src/__support/File/dir.h                 |  5 +-
 libc/src/__support/File/scan_impl.h           | 90 ++++++++++---------
 libc/test/src/__support/File/CMakeLists.txt   |  3 +-
 .../src/__support/File/scan_impl_test.cpp     |  3 +-
 5 files changed, 57 insertions(+), 48 deletions(-)

diff --git a/libc/src/__support/File/CMakeLists.txt b/libc/src/__support/File/CMakeLists.txt
index fa743fe3a124aa..33b2402b4f8479 100644
--- a/libc/src/__support/File/CMakeLists.txt
+++ b/libc/src/__support/File/CMakeLists.txt
@@ -37,9 +37,11 @@ add_header_library(
   HDRS
     scan_impl.h
   DEPENDS
-    libc.src.__support.CPP.vector
+    libc.src.__support.error_or
+    libc.src.__support.CPP.limits
     libc.src.stdlib.qsort_util
     libc.src.string.memory_utils.inline_memcpy
+    libc.hdr.errno_macros
     libc.hdr.types.struct_dirent
 )
 
diff --git a/libc/src/__support/File/dir.h b/libc/src/__support/File/dir.h
index 912b0c760fa4ef..544582cb0dff01 100644
--- a/libc/src/__support/File/dir.h
+++ b/libc/src/__support/File/dir.h
@@ -81,11 +81,14 @@ class Dir {
 public:
   static ErrorOr<Dir *> open(const char *path);
   static ErrorOr<Dir *> fdopen(int fd);
+  ErrorOr<struct dirent *> read();
   static ErrorOr<int> scan(const char *name, struct dirent ***namelist,
                            __scandir_filter_t filter,
                            __scandir_compare_t compare);
 
-  ErrorOr<struct dirent *> read();
+  LIBC_INLINE static size_t reclen(struct dirent *d) {
+    return platform_dir_reclen(d);
+  }
 
   // Returns 0 on success or the error number on failure. If an error number
   // was returned, then the resources associated with the directory are not
diff --git a/libc/src/__support/File/scan_impl.h b/libc/src/__support/File/scan_impl.h
index fea69ee36ec08b..0fa4c38b55d46b 100644
--- a/libc/src/__support/File/scan_impl.h
+++ b/libc/src/__support/File/scan_impl.h
@@ -14,13 +14,15 @@
 #ifndef LLVM_LIBC_SRC___SUPPORT_FILE_SCAN_IMPL_H
 #define LLVM_LIBC_SRC___SUPPORT_FILE_SCAN_IMPL_H
 
+#include "hdr/errno_macros.h"
 #include "hdr/func/free.h"
 #include "hdr/func/malloc.h"
+#include "hdr/func/realloc.h"
 #include "hdr/types/struct_dirent.h"
 #include "include/llvm-libc-types/__scandir_compare_t.h"
 #include "include/llvm-libc-types/__scandir_filter_t.h"
-#include "src/__support/CPP/vector.h"
-#include "src/__support/File/dir.h"
+#include "src/__support/CPP/limits.h"
+#include "src/__support/error_or.h"
 #include "src/stdlib/qsort_util.h"
 #include "src/string/memory_utils/inline_memcpy.h"
 
@@ -36,15 +38,21 @@ ErrorOr<int> scan_impl(const char *name, struct dirent ***namelist,
   }
   DirType *dir = res_open.value();
 
-  cpp::vector<struct dirent *> entries;
+  int saved_errno = 0;
+  struct dirent **entries = nullptr;
+  size_t count = 0;
+  size_t buffer_capacity = 0;
 
-  auto free_entries = [&entries]() {
-    for (struct dirent *entry : entries) {
-      ::free(entry);
+  auto free_entries = [&entries, &count]() {
+    if (entries == nullptr) {
+      return;
+    }
+    for (size_t i = 0; i < count; ++i) {
+      ::free(entries[i]);
     }
-  };
 
-  int saved_errno = 0;
+    ::free(entries);
+  };
 
   while (true) {
     auto res_read = dir->read();
@@ -58,14 +66,35 @@ ErrorOr<int> scan_impl(const char *name, struct dirent ***namelist,
       break;
     }
 
-    // Note, filter may modify errno
     if (filter != nullptr && !filter(entry)) {
       continue;
     }
 
-    // struct dirent contains an equivalent of a flexible array memeber, so
-    // we can't use sizeof and d_reclen member is only available on Linux.
-    size_t reclen = platform_dir_reclen(entry);
+    if (count >= buffer_capacity) {
+      size_t new_capacity = (buffer_capacity == 0) ? 8 : buffer_capacity * 2;
+      // Cap to MAX_INT since we must return the number of entreis as int
+      // anyway.
+      if (new_capacity > cpp::numeric_limits<int>::max()) {
+        new_capacity = cpp::numeric_limits<int>::max();
+      }
+      // Overflow check
+      if (new_capacity >
+          cpp::numeric_limits<size_t>::max() / sizeof(struct dirent *)) {
+        saved_errno = EOVERFLOW;
+        break;
+      }
+
+      struct dirent **bigger_buffer = static_cast<struct dirent **>(
+          ::realloc(entries, new_capacity * sizeof(struct dirent *)));
+      if (bigger_buffer == nullptr) {
+        saved_errno = ENOMEM;
+        break;
+      }
+      buffer_capacity = new_capacity;
+      entries = bigger_buffer;
+    }
+
+    size_t reclen = DirType::reclen(entry);
 
     struct dirent *new_entry = static_cast<struct dirent *>(::malloc(reclen));
     if (new_entry == nullptr) {
@@ -74,15 +103,10 @@ ErrorOr<int> scan_impl(const char *name, struct dirent ***namelist,
     }
     inline_memcpy(new_entry, entry, reclen);
 
-    if (!entries.push_back(new_entry)) {
-      ::free(new_entry);
-      saved_errno = ENOMEM;
-      break;
-    }
+    entries[count] = new_entry;
+    count++;
   }
 
-  // Closedir may modify errno and set it to, e.g. EBADF, which is not amongst
-  // POSIX-defined error codes for scandir.
   dir->close();
 
   if (saved_errno != 0) {
@@ -90,37 +114,17 @@ ErrorOr<int> scan_impl(const char *name, struct dirent ***namelist,
     return LIBC_NAMESPACE::Error(saved_errno);
   }
 
-  size_t alloc_size = entries.size() * sizeof(struct dirent *);
-  // The filter may have filtered out all entries. We'd like to avoid
-  // malloc(0) in this instance as its exact semantics might be
-  // implementation-dependent.
-  if (alloc_size == 0) {
-    alloc_size = sizeof(struct dirent *);
-  }
-
-  struct dirent **result = static_cast<struct dirent **>(::malloc(alloc_size));
-
-  if (result == nullptr) {
-    free_entries();
-    return LIBC_NAMESPACE::Error(ENOMEM);
-  }
-
-  if (compare != nullptr && !entries.empty()) {
+  if (compare != nullptr && count > 1) {
     auto cmp_fn = [compare](const void *a, const void *b) {
       auto left = static_cast<const struct dirent **>(const_cast<void *>(a));
       auto right = static_cast<const struct dirent **>(const_cast<void *>(b));
       return compare(left, right);
     };
-    internal::unstable_sort(entries.data(), entries.size(),
-                            sizeof(struct dirent *), cmp_fn);
-  }
-
-  for (size_t i = 0; i < entries.size(); ++i) {
-    result[i] = entries[i];
+    internal::unstable_sort(entries, count, sizeof(struct dirent *), cmp_fn);
   }
 
-  *namelist = result;
-  return static_cast<int>(entries.size());
+  *namelist = entries;
+  return static_cast<int>(count);
 }
 
 } // namespace internal
diff --git a/libc/test/src/__support/File/CMakeLists.txt b/libc/test/src/__support/File/CMakeLists.txt
index 778691a00c10cf..9101a7c5ee6898 100644
--- a/libc/test/src/__support/File/CMakeLists.txt
+++ b/libc/test/src/__support/File/CMakeLists.txt
@@ -54,8 +54,7 @@ add_libc_test(
   DEPENDS
     libc.hdr.types.struct_dirent
     libc.src.__support.File.scan_impl
-    libc.src.__support.File.dir
-    libc.src.__support.File.platform_dir
+    libc.src.__support.error_or
 )
 
 set(platform_stream_targets "platform_stdout;platform_stdin;platform_stderr")
diff --git a/libc/test/src/__support/File/scan_impl_test.cpp b/libc/test/src/__support/File/scan_impl_test.cpp
index 29df4c7162a3ea..956c6c9dafede1 100644
--- a/libc/test/src/__support/File/scan_impl_test.cpp
+++ b/libc/test/src/__support/File/scan_impl_test.cpp
@@ -35,12 +35,13 @@ struct MockDir {
       return LIBC_NAMESPACE::Error(read_errno_val);
     }
 
-    dummy_entry.d_reclen = sizeof(struct dirent);
     dummy_entry.d_name[0] = 'a';
     dummy_entry.d_name[1] = '\0';
     return &dummy_entry;
   }
 
+  static size_t reclen(struct dirent *) { return sizeof(struct dirent); }
+
   int close() {
     delete this;
     return 0;

>From 31361d4ed5a75a5565aedb97985f09f93903c6e6 Mon Sep 17 00:00:00 2001
From: Vadim Kotov <kotov at google.com>
Date: Thu, 24 Sep 2026 22:02:20 +0000
Subject: [PATCH 28/30] Remove unused dependency on free.h

---
 libc/src/__support/File/dir.cpp | 1 -
 1 file changed, 1 deletion(-)

diff --git a/libc/src/__support/File/dir.cpp b/libc/src/__support/File/dir.cpp
index 6e6e84c2829a10..4396149c6d8edc 100644
--- a/libc/src/__support/File/dir.cpp
+++ b/libc/src/__support/File/dir.cpp
@@ -14,7 +14,6 @@
 #include "src/__support/File/dir.h"
 
 #include "hdr/errno_macros.h"
-#include "hdr/func/free.h"
 #include "include/llvm-libc-types/__scandir_compare_t.h"
 #include "include/llvm-libc-types/__scandir_filter_t.h"
 #include "src/__support/CPP/mutex.h" // lock_guard

>From 86af096e7fa82f58d1dbc296f1e2d08c5a9fc684 Mon Sep 17 00:00:00 2001
From: Vadim Kotov <kotov at google.com>
Date: Thu, 24 Sep 2026 22:47:08 +0000
Subject: [PATCH 29/30] Fix dirent import bug in overlay mode.

---
 libc/include/llvm-libc-types/CMakeLists.txt        | 4 ++--
 libc/include/llvm-libc-types/__scandir_compare_t.h | 2 +-
 libc/include/llvm-libc-types/__scandir_filter_t.h  | 2 +-
 3 files changed, 4 insertions(+), 4 deletions(-)

diff --git a/libc/include/llvm-libc-types/CMakeLists.txt b/libc/include/llvm-libc-types/CMakeLists.txt
index 6464fdffa696bf..509445952169d2 100644
--- a/libc/include/llvm-libc-types/CMakeLists.txt
+++ b/libc/include/llvm-libc-types/CMakeLists.txt
@@ -761,5 +761,5 @@ add_header(__action_fn_t
     .VISIT
 )
 add_header(__free_fn_t HDR __free_fn_t.h)
-add_header(__scandir_filter_t HDR __scandir_filter_t.h DEPENDS .struct_dirent)
-add_header(__scandir_compare_t HDR __scandir_compare_t.h DEPENDS .struct_dirent)
+add_header(__scandir_filter_t HDR __scandir_filter_t.h)
+add_header(__scandir_compare_t HDR __scandir_compare_t.h)
diff --git a/libc/include/llvm-libc-types/__scandir_compare_t.h b/libc/include/llvm-libc-types/__scandir_compare_t.h
index 615955d0c60c03..caf678bf5b5bba 100644
--- a/libc/include/llvm-libc-types/__scandir_compare_t.h
+++ b/libc/include/llvm-libc-types/__scandir_compare_t.h
@@ -14,7 +14,7 @@
 #ifndef LLVM_LIBC_TYPES___SCANDIR_COMPARE_T_H
 #define LLVM_LIBC_TYPES___SCANDIR_COMPARE_T_H
 
-#include "struct_dirent.h"
+struct dirent;
 
 typedef int (*__scandir_compare_t)(const struct dirent **,
                                    const struct dirent **);
diff --git a/libc/include/llvm-libc-types/__scandir_filter_t.h b/libc/include/llvm-libc-types/__scandir_filter_t.h
index 3f6aa99889f42b..f49130b04a5ac7 100644
--- a/libc/include/llvm-libc-types/__scandir_filter_t.h
+++ b/libc/include/llvm-libc-types/__scandir_filter_t.h
@@ -14,7 +14,7 @@
 #ifndef LLVM_LIBC_TYPES___SCANDIR_FILTER_T_H
 #define LLVM_LIBC_TYPES___SCANDIR_FILTER_T_H
 
-#include "struct_dirent.h"
+struct dirent;
 
 typedef int (*__scandir_filter_t)(const struct dirent *);
 

>From 3536e7e0231434bc9a6d5f559eadcf9721385d14 Mon Sep 17 00:00:00 2001
From: Vadim Kotov <kotov at google.com>
Date: Fri, 25 Sep 2026 23:47:50 +0000
Subject: [PATCH 30/30] Fix include issues and minor cleanups.

---
 libc/include/dirent.yaml                        | 2 --
 libc/src/__support/File/dir.cpp                 | 6 +++---
 libc/src/__support/File/dir.h                   | 8 +++-----
 libc/src/__support/File/scan_impl.h             | 6 +++---
 libc/src/dirent/CMakeLists.txt                  | 1 -
 libc/src/dirent/scandir.cpp                     | 6 +++---
 libc/src/dirent/scandir.h                       | 4 ++--
 libc/test/src/__support/File/scan_impl_test.cpp | 3 +--
 8 files changed, 15 insertions(+), 21 deletions(-)

diff --git a/libc/include/dirent.yaml b/libc/include/dirent.yaml
index ca8d6c6619127d..cbea655ce5725c 100644
--- a/libc/include/dirent.yaml
+++ b/libc/include/dirent.yaml
@@ -26,8 +26,6 @@ types:
   - type_name: reclen_t
   - type_name: size_t
   - type_name: ssize_t
-  - type_name: __scandir_compare_t
-  - type_name: __scandir_filter_t
 functions:
   - name: alphasort
     standards:
diff --git a/libc/src/__support/File/dir.cpp b/libc/src/__support/File/dir.cpp
index 4396149c6d8edc..6c2b9fa5fc5e52 100644
--- a/libc/src/__support/File/dir.cpp
+++ b/libc/src/__support/File/dir.cpp
@@ -14,8 +14,6 @@
 #include "src/__support/File/dir.h"
 
 #include "hdr/errno_macros.h"
-#include "include/llvm-libc-types/__scandir_compare_t.h"
-#include "include/llvm-libc-types/__scandir_filter_t.h"
 #include "src/__support/CPP/mutex.h" // lock_guard
 #include "src/__support/CPP/new.h"
 #include "src/__support/File/scan_impl.h"
@@ -82,7 +80,9 @@ int Dir::close() {
 }
 
 ErrorOr<int> Dir::scan(const char *name, struct dirent ***namelist,
-                       __scandir_filter_t filter, __scandir_compare_t compare) {
+                       int (*filter)(const struct dirent *),
+                       int (*compare)(const struct dirent **,
+                                      const struct dirent **)) {
   return internal::scan_impl<Dir>(name, namelist, filter, compare);
 }
 
diff --git a/libc/src/__support/File/dir.h b/libc/src/__support/File/dir.h
index 544582cb0dff01..58f5276cc9ea16 100644
--- a/libc/src/__support/File/dir.h
+++ b/libc/src/__support/File/dir.h
@@ -14,15 +14,12 @@
 #ifndef LLVM_LIBC_SRC___SUPPORT_FILE_DIR_H
 #define LLVM_LIBC_SRC___SUPPORT_FILE_DIR_H
 
-#include "include/llvm-libc-types/__scandir_compare_t.h"
-#include "include/llvm-libc-types/__scandir_filter_t.h"
 #include "src/__support/CPP/span.h"
 #include "src/__support/error_or.h"
 #include "src/__support/macros/config.h"
 #include "src/__support/threads/mutex.h"
 
 #include "hdr/types/struct_dirent.h"
-#include <dirent.h>
 
 namespace LIBC_NAMESPACE_DECL {
 
@@ -83,8 +80,9 @@ class Dir {
   static ErrorOr<Dir *> fdopen(int fd);
   ErrorOr<struct dirent *> read();
   static ErrorOr<int> scan(const char *name, struct dirent ***namelist,
-                           __scandir_filter_t filter,
-                           __scandir_compare_t compare);
+                           int (*filter)(const struct dirent *),
+                           int (*compare)(const struct dirent **,
+                                          const struct dirent **));
 
   LIBC_INLINE static size_t reclen(struct dirent *d) {
     return platform_dir_reclen(d);
diff --git a/libc/src/__support/File/scan_impl.h b/libc/src/__support/File/scan_impl.h
index 0fa4c38b55d46b..a56d4fc8cdddad 100644
--- a/libc/src/__support/File/scan_impl.h
+++ b/libc/src/__support/File/scan_impl.h
@@ -19,8 +19,6 @@
 #include "hdr/func/malloc.h"
 #include "hdr/func/realloc.h"
 #include "hdr/types/struct_dirent.h"
-#include "include/llvm-libc-types/__scandir_compare_t.h"
-#include "include/llvm-libc-types/__scandir_filter_t.h"
 #include "src/__support/CPP/limits.h"
 #include "src/__support/error_or.h"
 #include "src/stdlib/qsort_util.h"
@@ -31,7 +29,9 @@ namespace internal {
 
 template <typename DirType>
 ErrorOr<int> scan_impl(const char *name, struct dirent ***namelist,
-                       __scandir_filter_t filter, __scandir_compare_t compare) {
+                       int (*filter)(const struct dirent *),
+                       int (*compare)(const struct dirent **,
+                                      const struct dirent **)) {
   auto res_open = DirType::open(name);
   if (!res_open) {
     return LIBC_NAMESPACE::Error(res_open.error());
diff --git a/libc/src/dirent/CMakeLists.txt b/libc/src/dirent/CMakeLists.txt
index 0079ccc182c7eb..b39f9a2a8e9ede 100644
--- a/libc/src/dirent/CMakeLists.txt
+++ b/libc/src/dirent/CMakeLists.txt
@@ -76,5 +76,4 @@ add_entrypoint_object(
     libc.src.__support.File.platform_dir
     libc.src.__support.macros.config
     libc.src.errno.errno
-    libc.include.dirent
 )
diff --git a/libc/src/dirent/scandir.cpp b/libc/src/dirent/scandir.cpp
index 0596e585a6edcd..c1966424f787e5 100644
--- a/libc/src/dirent/scandir.cpp
+++ b/libc/src/dirent/scandir.cpp
@@ -19,13 +19,13 @@
 #include "src/__support/libc_errno.h"
 #include "src/__support/macros/config.h"
 
-#include <dirent.h>
-
 namespace LIBC_NAMESPACE_DECL {
 
 LLVM_LIBC_FUNCTION(int, scandir,
                    (const char *dir, struct dirent ***namelist,
-                    __scandir_filter_t filter, __scandir_compare_t compare)) {
+                    int (*filter)(const struct dirent *),
+                    int (*compare)(const struct dirent **,
+                                   const struct dirent **))) {
 
   auto res = Dir::scan(dir, namelist, filter, compare);
   if (!res) {
diff --git a/libc/src/dirent/scandir.h b/libc/src/dirent/scandir.h
index 03ffe076c781fc..d53ac40f278c14 100644
--- a/libc/src/dirent/scandir.h
+++ b/libc/src/dirent/scandir.h
@@ -20,8 +20,8 @@
 namespace LIBC_NAMESPACE_DECL {
 
 int scandir(const char *dir, struct dirent ***namelist,
-            int (*sel)(const struct dirent *),
-            int (*compar)(const struct dirent **, const struct dirent **));
+            int (*filter)(const struct dirent *),
+            int (*compare)(const struct dirent **, const struct dirent **));
 
 } // namespace LIBC_NAMESPACE_DECL
 
diff --git a/libc/test/src/__support/File/scan_impl_test.cpp b/libc/test/src/__support/File/scan_impl_test.cpp
index 956c6c9dafede1..745fa4ced44866 100644
--- a/libc/test/src/__support/File/scan_impl_test.cpp
+++ b/libc/test/src/__support/File/scan_impl_test.cpp
@@ -24,8 +24,7 @@ struct MockDir {
 
   static struct dirent dummy_entry;
 
-  static LIBC_NAMESPACE::ErrorOr<MockDir *> open(const char *path) {
-    (void)path;
+  static LIBC_NAMESPACE::ErrorOr<MockDir *> open(const char *) {
     return new MockDir();
   }
 



More information about the libc-commits mailing list