[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