[libc-commits] [libc] [libc] Implement fdopendir (PR #206590)
Michael Jones via libc-commits
libc-commits at lists.llvm.org
Tue Jun 30 10:56:56 PDT 2026
https://github.com/michaelrj-google updated https://github.com/llvm/llvm-project/pull/206590
>From bcf158b60bc4e21fdfc9cbbea86d8f74caa4350a Mon Sep 17 00:00:00 2001
From: Michael Jones <michaelrj at google.com>
Date: Mon, 29 Jun 2026 21:24:22 +0000
Subject: [PATCH 1/2] [libc] Implement fdopendir
The fdopendir function takes a file descriptor opened on a directory,
then opens a DIR* on it. This PR also adds proxy headers for various dir
related structs.
Needed for #191075
Assisted-by: Automated tooling, human reviewed.
---
libc/config/linux/aarch64/entrypoints.txt | 1 +
libc/config/linux/riscv/entrypoints.txt | 1 +
libc/config/linux/x86_64/entrypoints.txt | 1 +
libc/hdr/CMakeLists.txt | 1 +
libc/hdr/dirent_overlay.h | 23 ++++
libc/hdr/types/CMakeLists.txt | 22 ++++
libc/hdr/types/DIR.h | 27 +++++
libc/hdr/types/struct_dirent.h | 27 +++++
libc/src/__support/File/dir.cpp | 18 +--
libc/src/__support/File/dir.h | 8 +-
libc/src/__support/File/linux/CMakeLists.txt | 5 +-
libc/src/__support/File/linux/dir.cpp | 45 +++++--
.../OSUtil/linux/stat/kernel_statx_types.h | 3 +
.../linux/syscall_wrappers/CMakeLists.txt | 12 ++
.../OSUtil/linux/syscall_wrappers/fcntl.h | 43 +++++++
libc/src/dirent/CMakeLists.txt | 15 +++
libc/src/dirent/fdopendir.cpp | 37 ++++++
libc/src/dirent/fdopendir.h | 26 ++++
libc/test/src/dirent/CMakeLists.txt | 22 ++++
libc/test/src/dirent/fdopendir_test.cpp | 113 ++++++++++++++++++
20 files changed, 432 insertions(+), 18 deletions(-)
create mode 100644 libc/hdr/dirent_overlay.h
create mode 100644 libc/hdr/types/DIR.h
create mode 100644 libc/hdr/types/struct_dirent.h
create mode 100644 libc/src/__support/OSUtil/linux/syscall_wrappers/fcntl.h
create mode 100644 libc/src/dirent/fdopendir.cpp
create mode 100644 libc/src/dirent/fdopendir.h
create mode 100644 libc/test/src/dirent/fdopendir_test.cpp
diff --git a/libc/config/linux/aarch64/entrypoints.txt b/libc/config/linux/aarch64/entrypoints.txt
index d6e627fdea421..99d2f1fdf1f3f 100644
--- a/libc/config/linux/aarch64/entrypoints.txt
+++ b/libc/config/linux/aarch64/entrypoints.txt
@@ -1019,6 +1019,7 @@ if(LLVM_LIBC_FULL_BUILD)
libc.src.dirent.dirfd
libc.src.dirent.opendir
libc.src.dirent.readdir
+ libc.src.dirent.fdopendir
# arpa/inet.h entrypoints
libc.src.arpa.inet.htonl
diff --git a/libc/config/linux/riscv/entrypoints.txt b/libc/config/linux/riscv/entrypoints.txt
index 3bb12384633a5..ce5d30f2ecd79 100644
--- a/libc/config/linux/riscv/entrypoints.txt
+++ b/libc/config/linux/riscv/entrypoints.txt
@@ -1152,6 +1152,7 @@ if(LLVM_LIBC_FULL_BUILD)
libc.src.dirent.dirfd
libc.src.dirent.opendir
libc.src.dirent.readdir
+ libc.src.dirent.fdopendir
# arpa/inet.h entrypoints
libc.src.arpa.inet.htonl
diff --git a/libc/config/linux/x86_64/entrypoints.txt b/libc/config/linux/x86_64/entrypoints.txt
index 81301fb5c4d65..9214abd4b3170 100644
--- a/libc/config/linux/x86_64/entrypoints.txt
+++ b/libc/config/linux/x86_64/entrypoints.txt
@@ -1223,6 +1223,7 @@ if(LLVM_LIBC_FULL_BUILD)
libc.src.dirent.dirfd
libc.src.dirent.opendir
libc.src.dirent.readdir
+ libc.src.dirent.fdopendir
# pthread.h entrypoints
libc.src.pthread.pthread_atfork
diff --git a/libc/hdr/CMakeLists.txt b/libc/hdr/CMakeLists.txt
index c24b826a6ef14..d9890fc5af7a1 100644
--- a/libc/hdr/CMakeLists.txt
+++ b/libc/hdr/CMakeLists.txt
@@ -126,6 +126,7 @@ add_proxy_header_library(
libc.include.llvm-libc-macros.stdlib_macros
)
+add_header_library(dirent_overlay HDRS dirent_overlay.h)
add_header_library(stdio_overlay HDRS stdio_overlay.h)
add_proxy_header_library(
diff --git a/libc/hdr/dirent_overlay.h b/libc/hdr/dirent_overlay.h
new file mode 100644
index 0000000000000..26dcace618a83
--- /dev/null
+++ b/libc/hdr/dirent_overlay.h
@@ -0,0 +1,23 @@
+//===----------------------------------------------------------------------===//
+//
+// 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
+/// Overlay header for dirent.h in overlay build mode.
+///
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_LIBC_HDR_DIRENT_OVERLAY_H
+#define LLVM_LIBC_HDR_DIRENT_OVERLAY_H
+
+#ifdef LIBC_FULL_BUILD
+#error "This header should only be included in overlay mode"
+#endif
+
+#include <dirent.h>
+
+#endif // LLVM_LIBC_HDR_DIRENT_OVERLAY_H
diff --git a/libc/hdr/types/CMakeLists.txt b/libc/hdr/types/CMakeLists.txt
index a130f7ee0000a..dc2e941de8a1c 100644
--- a/libc/hdr/types/CMakeLists.txt
+++ b/libc/hdr/types/CMakeLists.txt
@@ -87,6 +87,17 @@ add_proxy_header_library(
libc.include.llvm-libc-types.siginfo_t
)
+add_proxy_header_library(
+ struct_dirent
+ HDRS
+ struct_dirent.h
+ DEPENDS
+ libc.hdr.dirent_overlay
+ FULL_BUILD_DEPENDS
+ libc.include.llvm-libc-types.struct_dirent
+ libc.include.dirent
+)
+
add_proxy_header_library(
struct_epoll_event
HDRS
@@ -343,6 +354,17 @@ add_proxy_header_library(
libc.include.stdio
)
+add_proxy_header_library(
+ DIR
+ HDRS
+ DIR.h
+ DEPENDS
+ libc.hdr.dirent_overlay
+ FULL_BUILD_DEPENDS
+ libc.include.llvm-libc-types.DIR
+ libc.include.dirent
+)
+
add_proxy_header_library(
off_t
HDRS
diff --git a/libc/hdr/types/DIR.h b/libc/hdr/types/DIR.h
new file mode 100644
index 0000000000000..27a65e0ceff05
--- /dev/null
+++ b/libc/hdr/types/DIR.h
@@ -0,0 +1,27 @@
+//===----------------------------------------------------------------------===//
+//
+// 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
+/// Proxy for DIR.
+///
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_LIBC_HDR_TYPES_DIR_H
+#define LLVM_LIBC_HDR_TYPES_DIR_H
+
+#ifdef LIBC_FULL_BUILD
+
+#include "include/llvm-libc-types/DIR.h"
+
+#else // Overlay mode
+
+#include "hdr/dirent_overlay.h"
+
+#endif // LIBC_FULL_BUILD
+
+#endif // LLVM_LIBC_HDR_TYPES_DIR_H
diff --git a/libc/hdr/types/struct_dirent.h b/libc/hdr/types/struct_dirent.h
new file mode 100644
index 0000000000000..5e9e0bf974811
--- /dev/null
+++ b/libc/hdr/types/struct_dirent.h
@@ -0,0 +1,27 @@
+//===----------------------------------------------------------------------===//
+//
+// 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
+/// Proxy for struct dirent.
+///
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_LIBC_HDR_TYPES_STRUCT_DIRENT_H
+#define LLVM_LIBC_HDR_TYPES_STRUCT_DIRENT_H
+
+#ifdef LIBC_FULL_BUILD
+
+#include "include/llvm-libc-types/struct_dirent.h"
+
+#else // Overlay mode
+
+#include "hdr/dirent_overlay.h"
+
+#endif // LIBC_FULL_BUILD
+
+#endif // LLVM_LIBC_HDR_TYPES_STRUCT_DIRENT_H
diff --git a/libc/src/__support/File/dir.cpp b/libc/src/__support/File/dir.cpp
index eb33656808414..76a45b3df8c0a 100644
--- a/libc/src/__support/File/dir.cpp
+++ b/libc/src/__support/File/dir.cpp
@@ -17,19 +17,23 @@
namespace LIBC_NAMESPACE_DECL {
+ErrorOr<Dir *> Dir::fdopen(int fd) {
+ LIBC_NAMESPACE::AllocChecker ac;
+ Dir *dir = new (ac) Dir(fd);
+ if (!ac)
+ return LIBC_NAMESPACE::Error(ENOMEM);
+ return dir;
+}
+
ErrorOr<Dir *> Dir::open(const char *path) {
auto fd = platform_opendir(path);
if (!fd)
return LIBC_NAMESPACE::Error(fd.error());
- LIBC_NAMESPACE::AllocChecker ac;
- Dir *dir = new (ac) Dir(fd.value());
- if (!ac)
- return LIBC_NAMESPACE::Error(ENOMEM);
- return dir;
+ return Dir::fdopen(fd.value());
}
-ErrorOr<struct ::dirent *> Dir::read() {
+ErrorOr<struct dirent *> Dir::read() {
cpp::lock_guard lock(mutex);
if (readptr >= fillsize) {
auto readsize = platform_fetch_dirents(fd, buffer);
@@ -41,7 +45,7 @@ ErrorOr<struct ::dirent *> Dir::read() {
if (fillsize == 0)
return nullptr;
- struct ::dirent *d = reinterpret_cast<struct ::dirent *>(buffer + readptr);
+ struct dirent *d = reinterpret_cast<struct dirent *>(buffer + readptr);
#ifdef __linux__
// The d_reclen field is available on Linux but not required by POSIX.
readptr += d->d_reclen;
diff --git a/libc/src/__support/File/dir.h b/libc/src/__support/File/dir.h
index 247ac1225ceea..89f67ede0ecf5 100644
--- a/libc/src/__support/File/dir.h
+++ b/libc/src/__support/File/dir.h
@@ -31,6 +31,11 @@ int platform_closedir(int fd);
// failure.
ErrorOr<size_t> platform_fetch_dirents(int fd, cpp::span<uint8_t> buffer);
+// Platform specific function which checks if |fd| is a valid file descriptor
+// open for reading, and refers to a directory. Returns 0 on success, or the
+// error number on failure.
+int platform_check_dir(int fd);
+
// This class is designed to allow implementation of the POSIX dirent.h API.
// By itself, it is platform independent but calls platform specific
// functions to perform OS operations.
@@ -63,8 +68,9 @@ class Dir {
public:
static ErrorOr<Dir *> open(const char *path);
+ static ErrorOr<Dir *> fdopen(int fd);
- ErrorOr<struct ::dirent *> read();
+ ErrorOr<struct dirent *> read();
// 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/linux/CMakeLists.txt b/libc/src/__support/File/linux/CMakeLists.txt
index 7b770f916a251..613056718d234 100644
--- a/libc/src/__support/File/linux/CMakeLists.txt
+++ b/libc/src/__support/File/linux/CMakeLists.txt
@@ -26,9 +26,12 @@ add_object_library(
dir.cpp
DEPENDS
libc.hdr.fcntl_macros
- libc.include.sys_syscall
+ libc.hdr.sys_stat_macros
+ libc.src.__support.OSUtil.linux.stat.kernel_statx_types
libc.src.__support.OSUtil.osutil
libc.src.__support.OSUtil.linux.syscall_wrappers.open
+ libc.src.__support.OSUtil.linux.syscall_wrappers.statx
+ libc.src.__support.OSUtil.linux.syscall_wrappers.fcntl
libc.src.__support.error_or
libc.src.errno.errno
libc.src.__support.File.dir
diff --git a/libc/src/__support/File/linux/dir.cpp b/libc/src/__support/File/linux/dir.cpp
index 8412b42348559..77f9a51952c3b 100644
--- a/libc/src/__support/File/linux/dir.cpp
+++ b/libc/src/__support/File/linux/dir.cpp
@@ -1,21 +1,27 @@
-//===--- Linux implementation of the Dir helpers --------------------------===//
+//===----------------------------------------------------------------------===//
//
// 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
+/// Linux implementation of the Dir helpers.
+///
+//===----------------------------------------------------------------------===//
#include "src/__support/File/dir.h"
-
+#include "hdr/fcntl_macros.h" // For open flags
+#include "hdr/sys_stat_macros.h" // For S_ISDIR
+#include "src/__support/OSUtil/linux/stat/kernel_statx_types.h"
+#include "src/__support/OSUtil/linux/syscall_wrappers/fcntl.h"
#include "src/__support/OSUtil/linux/syscall_wrappers/open.h"
+#include "src/__support/OSUtil/linux/syscall_wrappers/statx.h"
#include "src/__support/OSUtil/syscall.h" // For internal syscall function.
#include "src/__support/error_or.h"
#include "src/__support/macros/config.h"
-#include "hdr/fcntl_macros.h" // For open flags
-#include <sys/syscall.h> // For syscall numbers
-
namespace LIBC_NAMESPACE_DECL {
ErrorOr<int> platform_opendir(const char *name) {
@@ -30,17 +36,38 @@ ErrorOr<size_t> platform_fetch_dirents(int fd, cpp::span<uint8_t> buffer) {
#error "getdents64 syscalls not available to perform a fetch dirents operation."
#endif
- if (size < 0) {
+ if (size < 0)
return LIBC_NAMESPACE::Error(static_cast<int>(-size));
- }
return size;
}
int platform_closedir(int fd) {
int ret = LIBC_NAMESPACE::syscall_impl<int>(SYS_close, fd);
- if (ret < 0) {
+ if (ret < 0)
return static_cast<int>(-ret);
- }
+ return 0;
+}
+
+int platform_check_dir(int fd) {
+ // 1. Verify fd is valid and open for reading.
+ auto flags = linux_syscalls::fcntl(fd, F_GETFL);
+ if (!flags)
+ return flags.error();
+
+ // Check if open for reading.
+ if ((flags.value() & O_PATH) || ((flags.value() & O_ACCMODE) == O_WRONLY))
+ return EBADF;
+
+ // 2. Verify fd refers to a directory.
+ internal::kernel_statx_buf xbuf;
+ auto result = linux_syscalls::statx(fd, "", AT_EMPTY_PATH,
+ internal::KERNEL_STATX_TYPE_MASK, &xbuf);
+ if (!result)
+ return result.error();
+
+ if (!S_ISDIR(xbuf.stx_mode))
+ return ENOTDIR;
+
return 0;
}
diff --git a/libc/src/__support/OSUtil/linux/stat/kernel_statx_types.h b/libc/src/__support/OSUtil/linux/stat/kernel_statx_types.h
index 080d48bb176ef..bbaf6d82a7d73 100644
--- a/libc/src/__support/OSUtil/linux/stat/kernel_statx_types.h
+++ b/libc/src/__support/OSUtil/linux/stat/kernel_statx_types.h
@@ -65,6 +65,9 @@ struct kernel_statx_buf {
// stx_btime field will be filled in.
constexpr unsigned int KERNEL_STATX_BASIC_STATS_MASK = 0x7FF;
+// This mask is used to check the type of the file descriptor.
+constexpr unsigned int KERNEL_STATX_TYPE_MASK = 0x008;
+
} // namespace internal
} // namespace LIBC_NAMESPACE_DECL
diff --git a/libc/src/__support/OSUtil/linux/syscall_wrappers/CMakeLists.txt b/libc/src/__support/OSUtil/linux/syscall_wrappers/CMakeLists.txt
index f3282c315d9a9..7f416bb5a2407 100644
--- a/libc/src/__support/OSUtil/linux/syscall_wrappers/CMakeLists.txt
+++ b/libc/src/__support/OSUtil/linux/syscall_wrappers/CMakeLists.txt
@@ -631,6 +631,18 @@ add_header_library(
libc.include.sys_syscall
)
+add_header_library(
+ fcntl
+ HDRS
+ fcntl.h
+ DEPENDS
+ libc.src.__support.OSUtil.osutil
+ libc.src.__support.common
+ libc.src.__support.error_or
+ libc.src.__support.macros.config
+ libc.include.sys_syscall
+)
+
add_header_library(
fsync
HDRS
diff --git a/libc/src/__support/OSUtil/linux/syscall_wrappers/fcntl.h b/libc/src/__support/OSUtil/linux/syscall_wrappers/fcntl.h
new file mode 100644
index 0000000000000..ce73fe71f18cb
--- /dev/null
+++ b/libc/src/__support/OSUtil/linux/syscall_wrappers/fcntl.h
@@ -0,0 +1,43 @@
+//===----------------------------------------------------------------------===//
+//
+// 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
+/// ErrorOr-returning syscall wrapper for fcntl.
+///
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_LIBC_SRC___SUPPORT_OSUTIL_SYSCALL_WRAPPERS_FCNTL_H
+#define LLVM_LIBC_SRC___SUPPORT_OSUTIL_SYSCALL_WRAPPERS_FCNTL_H
+
+#include "src/__support/OSUtil/linux/syscall.h" // syscall_impl
+#include "src/__support/common.h"
+#include "src/__support/error_or.h"
+#include "src/__support/macros/config.h"
+#include <sys/syscall.h> // For syscall numbers
+
+namespace LIBC_NAMESPACE_DECL {
+namespace linux_syscalls {
+
+LIBC_INLINE ErrorOr<int> fcntl(int fd, int cmd, void *arg) {
+ int ret = syscall_impl<int>(SYS_fcntl, fd, cmd, arg);
+ if (ret < 0)
+ return Error(-ret);
+ return ret;
+}
+
+LIBC_INLINE ErrorOr<int> fcntl(int fd, int cmd, long arg = 0) {
+ int ret = syscall_impl<int>(SYS_fcntl, fd, cmd, arg);
+ if (ret < 0)
+ return Error(-ret);
+ return ret;
+}
+
+} // namespace linux_syscalls
+} // namespace LIBC_NAMESPACE_DECL
+
+#endif // LLVM_LIBC_SRC___SUPPORT_OSUTIL_SYSCALL_WRAPPERS_FCNTL_H
diff --git a/libc/src/dirent/CMakeLists.txt b/libc/src/dirent/CMakeLists.txt
index eb4184693a43c..6482e7e04c991 100644
--- a/libc/src/dirent/CMakeLists.txt
+++ b/libc/src/dirent/CMakeLists.txt
@@ -48,3 +48,18 @@ add_entrypoint_object(
libc.src.__support.File.platform_dir
libc.src.errno.errno
)
+
+add_entrypoint_object(
+ fdopendir
+ SRCS
+ fdopendir.cpp
+ HDRS
+ fdopendir.h
+ DEPENDS
+ libc.include.dirent
+ libc.hdr.types.DIR
+ libc.src.__support.File.dir
+ libc.src.__support.File.platform_dir
+ libc.src.errno.errno
+)
+
diff --git a/libc/src/dirent/fdopendir.cpp b/libc/src/dirent/fdopendir.cpp
new file mode 100644
index 0000000000000..b92709c4a54e8
--- /dev/null
+++ b/libc/src/dirent/fdopendir.cpp
@@ -0,0 +1,37 @@
+//===----------------------------------------------------------------------===//
+//
+// 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 the POSIX fdopendir function.
+///
+//===----------------------------------------------------------------------===//
+
+#include "fdopendir.h"
+
+#include "src/__support/File/dir.h"
+#include "src/__support/common.h"
+#include "src/__support/libc_errno.h"
+
+namespace LIBC_NAMESPACE_DECL {
+
+LLVM_LIBC_FUNCTION(DIR *, fdopendir, (int fd)) {
+ int check = platform_check_dir(fd);
+ if (check != 0) {
+ libc_errno = check;
+ return nullptr;
+ }
+
+ auto dir = Dir::fdopen(fd);
+ if (!dir) {
+ libc_errno = dir.error();
+ return nullptr;
+ }
+ return reinterpret_cast<DIR *>(dir.value());
+}
+
+} // namespace LIBC_NAMESPACE_DECL
diff --git a/libc/src/dirent/fdopendir.h b/libc/src/dirent/fdopendir.h
new file mode 100644
index 0000000000000..d70f2f7cddecf
--- /dev/null
+++ b/libc/src/dirent/fdopendir.h
@@ -0,0 +1,26 @@
+//===----------------------------------------------------------------------===//
+//
+// 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
+/// Declaration of the POSIX fdopendir function.
+///
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_LIBC_SRC_DIRENT_FDOPENDIR_H
+#define LLVM_LIBC_SRC_DIRENT_FDOPENDIR_H
+
+#include "hdr/types/DIR.h"
+#include "src/__support/macros/config.h"
+
+namespace LIBC_NAMESPACE_DECL {
+
+DIR *fdopendir(int fd);
+
+} // namespace LIBC_NAMESPACE_DECL
+
+#endif // LLVM_LIBC_SRC_DIRENT_FDOPENDIR_H
diff --git a/libc/test/src/dirent/CMakeLists.txt b/libc/test/src/dirent/CMakeLists.txt
index 8db512129f893..af18006c5d122 100644
--- a/libc/test/src/dirent/CMakeLists.txt
+++ b/libc/test/src/dirent/CMakeLists.txt
@@ -17,3 +17,25 @@ add_libc_unittest(
libc.test.UnitTest.ErrnoCheckingTest
)
+add_libc_unittest(
+ fdopendir_test
+ SUITE
+ libc_dirent_unittests
+ SRCS
+ fdopendir_test.cpp
+ DEPENDS
+ libc.src.__support.CPP.string_view
+ libc.src.dirent.fdopendir
+ libc.src.dirent.closedir
+ libc.src.dirent.readdir
+ libc.src.fcntl.open
+ libc.src.unistd.close
+ libc.src.fcntl.fcntl
+ libc.src.errno.errno
+ libc.test.UnitTest.ErrnoCheckingTest
+ libc.test.UnitTest.ErrnoSetterMatcher
+ libc.hdr.types.DIR
+ libc.hdr.types.struct_dirent
+)
+
+
diff --git a/libc/test/src/dirent/fdopendir_test.cpp b/libc/test/src/dirent/fdopendir_test.cpp
new file mode 100644
index 0000000000000..43e77f583982c
--- /dev/null
+++ b/libc/test/src/dirent/fdopendir_test.cpp
@@ -0,0 +1,113 @@
+//===----------------------------------------------------------------------===//
+//
+// 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
+/// Unit tests for the POSIX fdopendir function.
+///
+//===----------------------------------------------------------------------===//
+
+#include "hdr/types/DIR.h"
+#include "hdr/types/struct_dirent.h"
+#include "src/dirent/closedir.h"
+#include "src/dirent/dirfd.h"
+#include "src/dirent/fdopendir.h"
+#include "src/dirent/readdir.h"
+#include "src/fcntl/fcntl.h"
+#include "src/fcntl/open.h"
+#include "src/unistd/close.h"
+
+#include "src/__support/CPP/string_view.h"
+#include "test/UnitTest/ErrnoCheckingTest.h"
+#include "test/UnitTest/ErrnoSetterMatcher.h"
+#include "test/UnitTest/Test.h"
+
+#include "hdr/fcntl_macros.h"
+
+using LlvmLibcFdopendirTest = LIBC_NAMESPACE::testing::ErrnoCheckingTest;
+using LIBC_NAMESPACE::testing::ErrnoSetterMatcher::Succeeds;
+
+TEST_F(LlvmLibcFdopendirTest, SuccessCase) {
+ int fd = LIBC_NAMESPACE::open("testdata", O_RDONLY | O_DIRECTORY);
+ ASSERT_GT(fd, 0);
+ ASSERT_ERRNO_SUCCESS();
+
+ DIR *dir = LIBC_NAMESPACE::fdopendir(fd);
+ ASSERT_TRUE(dir != nullptr);
+ ASSERT_ERRNO_SUCCESS();
+
+ struct dirent *file1 = nullptr, *file2 = nullptr;
+ while (true) {
+ struct dirent *d = LIBC_NAMESPACE::readdir(dir);
+ if (d == nullptr)
+ break;
+ if (LIBC_NAMESPACE::cpp::string_view(&d->d_name[0]) == "file1.txt")
+ file1 = d;
+ if (LIBC_NAMESPACE::cpp::string_view(&d->d_name[0]) == "file2.txt")
+ file2 = d;
+ }
+ ASSERT_ERRNO_SUCCESS();
+ ASSERT_TRUE(file1 != nullptr);
+ ASSERT_TRUE(file2 != nullptr);
+
+ ASSERT_EQ(LIBC_NAMESPACE::closedir(dir), 0);
+
+ // Verify fd is closed
+ int fcntl_res = LIBC_NAMESPACE::fcntl(fd, F_GETFD);
+ ASSERT_EQ(fcntl_res, -1);
+ ASSERT_ERRNO_EQ(EBADF);
+}
+
+TEST_F(LlvmLibcFdopendirTest, InvalidFd) {
+ DIR *dir = LIBC_NAMESPACE::fdopendir(-1);
+ ASSERT_TRUE(dir == nullptr);
+ ASSERT_ERRNO_EQ(EBADF);
+}
+
+TEST_F(LlvmLibcFdopendirTest, ClosedFd) {
+ int fd = LIBC_NAMESPACE::open("testdata", O_RDONLY | O_DIRECTORY);
+ ASSERT_GT(fd, 0);
+ ASSERT_THAT(LIBC_NAMESPACE::close(fd), Succeeds(0));
+
+ DIR *dir = LIBC_NAMESPACE::fdopendir(fd);
+ ASSERT_TRUE(dir == nullptr);
+ ASSERT_ERRNO_EQ(EBADF);
+}
+
+TEST_F(LlvmLibcFdopendirTest, NotADirectory) {
+ int fd = LIBC_NAMESPACE::open("testdata/file1.txt", O_RDONLY);
+ ASSERT_GT(fd, 0);
+
+ DIR *dir = LIBC_NAMESPACE::fdopendir(fd);
+ ASSERT_TRUE(dir == nullptr);
+ ASSERT_ERRNO_EQ(ENOTDIR);
+
+ ASSERT_THAT(LIBC_NAMESPACE::close(fd), Succeeds(0));
+}
+
+TEST_F(LlvmLibcFdopendirTest, OPathFd) {
+ int fd = LIBC_NAMESPACE::open("testdata", O_PATH);
+ ASSERT_GT(fd, 0);
+
+ DIR *dir = LIBC_NAMESPACE::fdopendir(fd);
+ ASSERT_TRUE(dir == nullptr);
+ ASSERT_ERRNO_EQ(EBADF);
+
+ ASSERT_THAT(LIBC_NAMESPACE::close(fd), Succeeds(0));
+}
+
+TEST_F(LlvmLibcFdopendirTest, WriteOnlyFd) {
+ int fd = LIBC_NAMESPACE::open("testdata/file1.txt", O_WRONLY);
+ ASSERT_GT(fd, 0);
+
+ DIR *dir = LIBC_NAMESPACE::fdopendir(fd);
+ ASSERT_TRUE(dir == nullptr);
+ ASSERT_TRUE(libc_errno == EBADF || libc_errno == ENOTDIR);
+ libc_errno = 0;
+
+ ASSERT_THAT(LIBC_NAMESPACE::close(fd), Succeeds(0));
+}
>From 3938f94d66181fec67f241c59ee92fdd2817132a Mon Sep 17 00:00:00 2001
From: Michael Jones <michaelrj at google.com>
Date: Tue, 30 Jun 2026 17:56:31 +0000
Subject: [PATCH 2/2] address comments
---
libc/config/linux/arm/entrypoints.txt | 7 +++++++
libc/hdr/types/CMakeLists.txt | 2 --
libc/src/__support/File/linux/CMakeLists.txt | 2 ++
libc/src/dirent/CMakeLists.txt | 4 ++--
libc/test/src/dirent/fdopendir_test.cpp | 4 ++--
5 files changed, 13 insertions(+), 6 deletions(-)
diff --git a/libc/config/linux/arm/entrypoints.txt b/libc/config/linux/arm/entrypoints.txt
index 1b586fe90f91a..2144cb2bfdefd 100644
--- a/libc/config/linux/arm/entrypoints.txt
+++ b/libc/config/linux/arm/entrypoints.txt
@@ -228,6 +228,13 @@ if(LLVM_LIBC_FULL_BUILD)
libc.src.setjmp.setjmp
libc.src.setjmp.siglongjmp
libc.src.setjmp.sigsetjmp
+
+ # dirent.h entrypoints
+ libc.src.dirent.closedir
+ libc.src.dirent.dirfd
+ libc.src.dirent.opendir
+ libc.src.dirent.readdir
+ libc.src.dirent.fdopendir
)
endif()
diff --git a/libc/hdr/types/CMakeLists.txt b/libc/hdr/types/CMakeLists.txt
index dc2e941de8a1c..9ef96a09c2d4c 100644
--- a/libc/hdr/types/CMakeLists.txt
+++ b/libc/hdr/types/CMakeLists.txt
@@ -95,7 +95,6 @@ add_proxy_header_library(
libc.hdr.dirent_overlay
FULL_BUILD_DEPENDS
libc.include.llvm-libc-types.struct_dirent
- libc.include.dirent
)
add_proxy_header_library(
@@ -362,7 +361,6 @@ add_proxy_header_library(
libc.hdr.dirent_overlay
FULL_BUILD_DEPENDS
libc.include.llvm-libc-types.DIR
- libc.include.dirent
)
add_proxy_header_library(
diff --git a/libc/src/__support/File/linux/CMakeLists.txt b/libc/src/__support/File/linux/CMakeLists.txt
index 613056718d234..ba64ecf41a76e 100644
--- a/libc/src/__support/File/linux/CMakeLists.txt
+++ b/libc/src/__support/File/linux/CMakeLists.txt
@@ -25,6 +25,8 @@ add_object_library(
SRCS
dir.cpp
DEPENDS
+ libc.src.__support.common
+ libc.src.__support.macros.config
libc.hdr.fcntl_macros
libc.hdr.sys_stat_macros
libc.src.__support.OSUtil.linux.stat.kernel_statx_types
diff --git a/libc/src/dirent/CMakeLists.txt b/libc/src/dirent/CMakeLists.txt
index 6482e7e04c991..ea17bd4cfbba3 100644
--- a/libc/src/dirent/CMakeLists.txt
+++ b/libc/src/dirent/CMakeLists.txt
@@ -56,10 +56,10 @@ add_entrypoint_object(
HDRS
fdopendir.h
DEPENDS
- libc.include.dirent
+ libc.src.__support.common
+ libc.src.__support.macros.config
libc.hdr.types.DIR
libc.src.__support.File.dir
- libc.src.__support.File.platform_dir
libc.src.errno.errno
)
diff --git a/libc/test/src/dirent/fdopendir_test.cpp b/libc/test/src/dirent/fdopendir_test.cpp
index 43e77f583982c..29db66bcc1636 100644
--- a/libc/test/src/dirent/fdopendir_test.cpp
+++ b/libc/test/src/dirent/fdopendir_test.cpp
@@ -45,9 +45,9 @@ TEST_F(LlvmLibcFdopendirTest, SuccessCase) {
struct dirent *d = LIBC_NAMESPACE::readdir(dir);
if (d == nullptr)
break;
- if (LIBC_NAMESPACE::cpp::string_view(&d->d_name[0]) == "file1.txt")
+ if (LIBC_NAMESPACE::cpp::string_view(d->d_name) == "file1.txt")
file1 = d;
- if (LIBC_NAMESPACE::cpp::string_view(&d->d_name[0]) == "file2.txt")
+ if (LIBC_NAMESPACE::cpp::string_view(d->d_name) == "file2.txt")
file2 = d;
}
ASSERT_ERRNO_SUCCESS();
More information about the libc-commits
mailing list