[libc-commits] [libc] [libc] Implement scandir and its unit tests (PR #223198)
Vadim Kotov via libc-commits
libc-commits at lists.llvm.org
Thu Sep 24 15:47:39 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/29] 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/29] 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/29] 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/29] 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/29] 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/29] 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/29] 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/29] 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/29] 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/29] 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/29] 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/29] 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/29] 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/29] 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/29] 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/29] 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/29] 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/29] 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/29] 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/29] 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/29] 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/29] 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/29] 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/29] 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/29] 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/29] 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/29] 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/29] 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/29] 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 *);
More information about the libc-commits
mailing list