[libc] Implement fdopendir (#206590)

The fdopendir function takes a file descriptor opened on a directory,
then opens a DIR* on it.

Needed for #191075

Assisted-by: Automated tooling, human reviewed.
This commit is contained in:
Michael Jones 2026-07-06 11:22:42 -07:00 committed by GitHub
parent a4107d67f9
commit 7b9a58e6cf
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
21 changed files with 440 additions and 19 deletions

View file

@ -1022,6 +1022,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

View file

@ -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()

View file

@ -1155,6 +1155,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

View file

@ -1226,6 +1226,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

View file

@ -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(

23
libc/hdr/dirent_overlay.h Normal file
View file

@ -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

View file

@ -87,6 +87,16 @@ 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
)
add_proxy_header_library(
struct_epoll_event
HDRS
@ -343,6 +353,16 @@ 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
)
add_proxy_header_library(
off_t
HDRS

27
libc/hdr/types/DIR.h Normal file
View file

@ -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

View file

@ -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

View file

@ -17,19 +17,23 @@
namespace LIBC_NAMESPACE_DECL {
ErrorOr<Dir *> Dir::open(const char *path) {
auto fd = platform_opendir(path);
if (!fd)
return LIBC_NAMESPACE::Error(fd.error());
ErrorOr<Dir *> Dir::fdopen(int fd) {
LIBC_NAMESPACE::AllocChecker ac;
Dir *dir = new (ac) Dir(fd.value());
Dir *dir = new (ac) Dir(fd);
if (!ac)
return LIBC_NAMESPACE::Error(ENOMEM);
return dir;
}
ErrorOr<struct ::dirent *> Dir::read() {
ErrorOr<Dir *> Dir::open(const char *path) {
auto fd = platform_opendir(path);
if (!fd)
return LIBC_NAMESPACE::Error(fd.error());
return Dir::fdopen(fd.value());
}
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;

View file

@ -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

View file

@ -25,10 +25,15 @@ add_object_library(
SRCS
dir.cpp
DEPENDS
libc.src.__support.common
libc.src.__support.macros.config
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

View file

@ -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;
}

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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.src.__support.common
libc.src.__support.macros.config
libc.hdr.types.DIR
libc.src.__support.File.dir
libc.src.errno.errno
)

View file

@ -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

View file

@ -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

View file

@ -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
)

View file

@ -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) == "file1.txt")
file1 = d;
if (LIBC_NAMESPACE::cpp::string_view(d->d_name) == "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));
}