[libc-commits] [libc] [libc] Implement fstatat in sys/stat (PR #224334)
Aman Maurya via libc-commits
libc-commits at lists.llvm.org
Thu Sep 17 23:55:56 PDT 2026
https://github.com/amanmaurya92 updated https://github.com/llvm/llvm-project/pull/224334
>From f943d3dfd540b01bbfacfefca217196d3c682a74 Mon Sep 17 00:00:00 2001
From: amanmaurya92 <amanmaurya9209 at gmail.com>
Date: Thu, 17 Sep 2026 20:38:24 +0530
Subject: [PATCH 1/3] [libc] Implement fstatat in sys/stat
Implement the standard POSIX.1-2008 function fstatat in <sys/stat.h>.
Fixes #224327
Assisted by Antigravity.
---
libc/config/linux/aarch64/entrypoints.txt | 1 +
libc/config/linux/riscv/entrypoints.txt | 1 +
libc/config/linux/x86_64/entrypoints.txt | 1 +
libc/include/sys/stat.yaml | 7 ++
libc/src/sys/stat/CMakeLists.txt | 7 ++
libc/src/sys/stat/fstatat.h | 22 ++++++
libc/src/sys/stat/linux/CMakeLists.txt | 14 ++++
libc/src/sys/stat/linux/fstatat.cpp | 31 ++++++++
libc/test/src/sys/stat/CMakeLists.txt | 19 +++++
libc/test/src/sys/stat/fstatat_test.cpp | 92 +++++++++++++++++++++++
10 files changed, 195 insertions(+)
create mode 100644 libc/src/sys/stat/fstatat.h
create mode 100644 libc/src/sys/stat/linux/fstatat.cpp
create mode 100644 libc/test/src/sys/stat/fstatat_test.cpp
diff --git a/libc/config/linux/aarch64/entrypoints.txt b/libc/config/linux/aarch64/entrypoints.txt
index 1a05ed55365b4..ec1471b3bd018 100644
--- a/libc/config/linux/aarch64/entrypoints.txt
+++ b/libc/config/linux/aarch64/entrypoints.txt
@@ -350,6 +350,7 @@ set(TARGET_LIBC_ENTRYPOINTS
libc.src.sys.stat.fchmod
libc.src.sys.stat.fchmodat
libc.src.sys.stat.fstat
+ libc.src.sys.stat.fstatat
libc.src.sys.stat.futimens
libc.src.sys.stat.lstat
libc.src.sys.stat.mkdir
diff --git a/libc/config/linux/riscv/entrypoints.txt b/libc/config/linux/riscv/entrypoints.txt
index ba3600966fabf..f19a7cbc1df63 100644
--- a/libc/config/linux/riscv/entrypoints.txt
+++ b/libc/config/linux/riscv/entrypoints.txt
@@ -381,6 +381,7 @@ set(TARGET_LIBC_ENTRYPOINTS
libc.src.sys.stat.fchmod
libc.src.sys.stat.fchmodat
libc.src.sys.stat.fstat
+ libc.src.sys.stat.fstatat
libc.src.sys.stat.futimens
libc.src.sys.stat.lstat
libc.src.sys.stat.mkdir
diff --git a/libc/config/linux/x86_64/entrypoints.txt b/libc/config/linux/x86_64/entrypoints.txt
index a32e83236e277..28a3b445b24c2 100644
--- a/libc/config/linux/x86_64/entrypoints.txt
+++ b/libc/config/linux/x86_64/entrypoints.txt
@@ -386,6 +386,7 @@ set(TARGET_LIBC_ENTRYPOINTS
libc.src.sys.stat.fchmod
libc.src.sys.stat.fchmodat
libc.src.sys.stat.fstat
+ libc.src.sys.stat.fstatat
libc.src.sys.stat.futimens
libc.src.sys.stat.lstat
libc.src.sys.stat.mkdir
diff --git a/libc/include/sys/stat.yaml b/libc/include/sys/stat.yaml
index 3c75ce3d796cb..9deb589475104 100644
--- a/libc/include/sys/stat.yaml
+++ b/libc/include/sys/stat.yaml
@@ -42,6 +42,13 @@ functions:
arguments:
- type: int
- type: struct stat *
+ - name: fstatat
+ return_type: int
+ arguments:
+ - type: int
+ - type: const char *__restrict
+ - type: struct stat *__restrict
+ - type: int
- name: lstat
return_type: int
arguments:
diff --git a/libc/src/sys/stat/CMakeLists.txt b/libc/src/sys/stat/CMakeLists.txt
index ae1a3f76078f1..952d44d11400d 100644
--- a/libc/src/sys/stat/CMakeLists.txt
+++ b/libc/src/sys/stat/CMakeLists.txt
@@ -30,6 +30,13 @@ add_entrypoint_object(
.${LIBC_TARGET_OS}.fstat
)
+add_entrypoint_object(
+ fstatat
+ ALIAS
+ DEPENDS
+ .${LIBC_TARGET_OS}.fstatat
+)
+
add_entrypoint_object(
lstat
ALIAS
diff --git a/libc/src/sys/stat/fstatat.h b/libc/src/sys/stat/fstatat.h
new file mode 100644
index 0000000000000..0f0e69c9e9c02
--- /dev/null
+++ b/libc/src/sys/stat/fstatat.h
@@ -0,0 +1,22 @@
+//===-- Implementation header for fstatat -----------------------*- C++ -*-===//
+//
+// 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
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_LIBC_SRC_SYS_STAT_FSTATAT_H
+#define LLVM_LIBC_SRC_SYS_STAT_FSTATAT_H
+
+#include "hdr/types/struct_stat.h"
+#include "src/__support/macros/config.h"
+
+namespace LIBC_NAMESPACE_DECL {
+
+int fstatat(int dirfd, const char *__restrict path,
+ struct stat *__restrict statbuf, int flags);
+
+} // namespace LIBC_NAMESPACE_DECL
+
+#endif // LLVM_LIBC_SRC_SYS_STAT_FSTATAT_H
diff --git a/libc/src/sys/stat/linux/CMakeLists.txt b/libc/src/sys/stat/linux/CMakeLists.txt
index b087fc927459c..2b424e9919580 100644
--- a/libc/src/sys/stat/linux/CMakeLists.txt
+++ b/libc/src/sys/stat/linux/CMakeLists.txt
@@ -116,6 +116,20 @@ add_entrypoint_object(
libc.src.__support.OSUtil.linux.stat.stat_via_statx
)
+add_entrypoint_object(
+ fstatat
+ SRCS
+ fstatat.cpp
+ HDRS
+ ../fstatat.h
+ DEPENDS
+ libc.hdr.fcntl_macros
+ libc.hdr.types.struct_stat
+ libc.src.__support.error_or
+ libc.src.__support.libc_errno
+ libc.src.__support.OSUtil.linux.stat.stat_via_statx
+)
+
add_entrypoint_object(
utimensat
SRCS
diff --git a/libc/src/sys/stat/linux/fstatat.cpp b/libc/src/sys/stat/linux/fstatat.cpp
new file mode 100644
index 0000000000000..7ee0efe8f6c8b
--- /dev/null
+++ b/libc/src/sys/stat/linux/fstatat.cpp
@@ -0,0 +1,31 @@
+//===-- Linux implementation of fstatat -----------------------------------===//
+//
+// 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
+//
+//===----------------------------------------------------------------------===//
+
+#include "src/sys/stat/fstatat.h"
+#include "hdr/fcntl_macros.h"
+#include "hdr/types/struct_stat.h"
+#include "src/__support/OSUtil/linux/stat/stat_via_statx.h"
+#include "src/__support/common.h"
+#include "src/__support/error_or.h"
+#include "src/__support/libc_errno.h"
+#include "src/__support/macros/config.h"
+
+namespace LIBC_NAMESPACE_DECL {
+
+LLVM_LIBC_FUNCTION(int, fstatat,
+ (int dirfd, const char *__restrict path,
+ struct stat *__restrict statbuf, int flags)) {
+ ErrorOr<void> result = internal::stat_via_statx(dirfd, path, flags, statbuf);
+ if (!result) {
+ libc_errno = result.error();
+ return -1;
+ }
+ return 0;
+}
+
+} // namespace LIBC_NAMESPACE_DECL
diff --git a/libc/test/src/sys/stat/CMakeLists.txt b/libc/test/src/sys/stat/CMakeLists.txt
index 775348f8c18fa..a0925810e404d 100644
--- a/libc/test/src/sys/stat/CMakeLists.txt
+++ b/libc/test/src/sys/stat/CMakeLists.txt
@@ -153,6 +153,25 @@ add_libc_test(
libc.test.UnitTest.ErrnoSetterMatcher
)
+add_libc_test(
+ fstatat_test
+ SUITE
+ libc_sys_stat_unittests
+ SRCS
+ fstatat_test.cpp
+ DEPENDS
+ libc.hdr.fcntl_macros
+ libc.hdr.sys_stat_macros
+ libc.hdr.types.struct_stat
+ libc.src.errno.errno
+ libc.src.sys.stat.fstatat
+ libc.src.fcntl.open
+ libc.src.unistd.close
+ libc.src.unistd.unlink
+ libc.test.UnitTest.ErrnoCheckingTest
+ libc.test.UnitTest.ErrnoSetterMatcher
+)
+
add_libc_test(
utimensat_test
SUITE
diff --git a/libc/test/src/sys/stat/fstatat_test.cpp b/libc/test/src/sys/stat/fstatat_test.cpp
new file mode 100644
index 0000000000000..e5aabd47c1b8a
--- /dev/null
+++ b/libc/test/src/sys/stat/fstatat_test.cpp
@@ -0,0 +1,92 @@
+//===-- Unittests for fstatat ---------------------------------------------===//
+//
+// 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
+//
+//===----------------------------------------------------------------------===//
+
+#include "hdr/fcntl_macros.h"
+#include "hdr/sys_stat_macros.h"
+#include "hdr/types/struct_stat.h"
+#include "src/fcntl/open.h"
+#include "src/sys/stat/fstatat.h"
+#include "src/unistd/close.h"
+#include "src/unistd/unlink.h"
+#include "test/UnitTest/ErrnoCheckingTest.h"
+#include "test/UnitTest/ErrnoSetterMatcher.h"
+#include "test/UnitTest/Test.h"
+
+using namespace LIBC_NAMESPACE::testing::ErrnoSetterMatcher;
+using LlvmLibcFstatatTest = LIBC_NAMESPACE::testing::ErrnoCheckingTest;
+
+TEST_F(LlvmLibcFstatatTest, StatWithAtFdcwd) {
+ constexpr const char *TEST_FILE = "testdata/fstatat.test";
+
+ int fd = LIBC_NAMESPACE::open(TEST_FILE, O_CREAT | O_WRONLY, S_IRWXU);
+ ASSERT_GT(fd, 0);
+ ASSERT_ERRNO_SUCCESS();
+ ASSERT_THAT(LIBC_NAMESPACE::close(fd), Succeeds(0));
+
+ struct stat statbuf;
+ ASSERT_THAT(LIBC_NAMESPACE::fstatat(AT_FDCWD, TEST_FILE, &statbuf, 0),
+ Succeeds(0));
+
+ ASSERT_EQ(int(statbuf.st_mode), int(S_IRWXU | S_IFREG));
+
+ ASSERT_THAT(LIBC_NAMESPACE::unlink(TEST_FILE), Succeeds(0));
+}
+
+TEST_F(LlvmLibcFstatatTest, StatWithDirFd) {
+ constexpr const char *TEST_DIR = "testdata";
+ constexpr const char *TEST_FILE = "testdata/fstatat_dir.test";
+ constexpr const char *TEST_FILE_BASENAME = "fstatat_dir.test";
+
+ int fd = LIBC_NAMESPACE::open(TEST_FILE, O_CREAT | O_WRONLY, S_IRWXU);
+ ASSERT_GT(fd, 0);
+ ASSERT_ERRNO_SUCCESS();
+ ASSERT_THAT(LIBC_NAMESPACE::close(fd), Succeeds(0));
+
+ int dirfd = LIBC_NAMESPACE::open(TEST_DIR, O_DIRECTORY);
+ ASSERT_GT(dirfd, 0);
+ ASSERT_ERRNO_SUCCESS();
+
+ struct stat statbuf;
+ ASSERT_THAT(LIBC_NAMESPACE::fstatat(dirfd, TEST_FILE_BASENAME, &statbuf, 0),
+ Succeeds(0));
+
+ ASSERT_EQ(int(statbuf.st_mode), int(S_IRWXU | S_IFREG));
+
+ ASSERT_THAT(LIBC_NAMESPACE::close(dirfd), Succeeds(0));
+ ASSERT_THAT(LIBC_NAMESPACE::unlink(TEST_FILE), Succeeds(0));
+}
+
+TEST_F(LlvmLibcFstatatTest, StatEmptyPath) {
+ constexpr const char *TEST_FILE = "testdata/fstatat_empty.test";
+
+ int fd = LIBC_NAMESPACE::open(TEST_FILE, O_CREAT | O_WRONLY, S_IRWXU);
+ ASSERT_GT(fd, 0);
+ ASSERT_ERRNO_SUCCESS();
+
+ struct stat statbuf;
+ ASSERT_THAT(LIBC_NAMESPACE::fstatat(fd, "", &statbuf, AT_EMPTY_PATH),
+ Succeeds(0));
+
+ ASSERT_EQ(int(statbuf.st_mode), int(S_IRWXU | S_IFREG));
+
+ ASSERT_THAT(LIBC_NAMESPACE::close(fd), Succeeds(0));
+ ASSERT_THAT(LIBC_NAMESPACE::unlink(TEST_FILE), Succeeds(0));
+}
+
+TEST_F(LlvmLibcFstatatTest, NonExistentFile) {
+ struct stat statbuf;
+ ASSERT_THAT(
+ LIBC_NAMESPACE::fstatat(AT_FDCWD, "non-existent-file", &statbuf, 0),
+ Fails(ENOENT));
+}
+
+TEST_F(LlvmLibcFstatatTest, BadDirFd) {
+ struct stat statbuf;
+ ASSERT_THAT(LIBC_NAMESPACE::fstatat(-1, "some-file", &statbuf, 0),
+ Fails(EBADF));
+}
>From fad0e6bd9492f646fdf6b6dfb7584a71884b8653 Mon Sep 17 00:00:00 2001
From: Aman Maurya <amanmaurya9209 at gmail.com>
Date: Fri, 18 Sep 2026 12:16:26 +0530
Subject: [PATCH 2/3] Update libc/test/src/sys/stat/fstatat_test.cpp
Co-authored-by: Pavel Labath <pavel at labath.sk>
---
libc/test/src/sys/stat/fstatat_test.cpp | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/libc/test/src/sys/stat/fstatat_test.cpp b/libc/test/src/sys/stat/fstatat_test.cpp
index e5aabd47c1b8a..0e3991ffb16b7 100644
--- a/libc/test/src/sys/stat/fstatat_test.cpp
+++ b/libc/test/src/sys/stat/fstatat_test.cpp
@@ -55,7 +55,7 @@ TEST_F(LlvmLibcFstatatTest, StatWithDirFd) {
ASSERT_THAT(LIBC_NAMESPACE::fstatat(dirfd, TEST_FILE_BASENAME, &statbuf, 0),
Succeeds(0));
- ASSERT_EQ(int(statbuf.st_mode), int(S_IRWXU | S_IFREG));
+ ASSERT_EQ(statbuf.st_mode, static_cast<mode_t>(S_IRWXU | S_IFREG));
ASSERT_THAT(LIBC_NAMESPACE::close(dirfd), Succeeds(0));
ASSERT_THAT(LIBC_NAMESPACE::unlink(TEST_FILE), Succeeds(0));
>From 5f29c8fbec4af6c59386b4431f8bd23553bd6601 Mon Sep 17 00:00:00 2001
From: amanmaurya92 <amanmaurya9209 at gmail.com>
Date: Fri, 18 Sep 2026 12:25:38 +0530
Subject: [PATCH 3/3] [libc] Address review comments for fstatat tests
- Use static_cast<mode_t> for mode checks across all test cases
- Use cpp::scope_exit to clean up files and directory descriptors on failure
- Update CMake dependencies with mode_t and scope headers
---
libc/test/src/sys/stat/CMakeLists.txt | 2 ++
libc/test/src/sys/stat/fstatat_test.cpp | 24 ++++++++++++++----------
2 files changed, 16 insertions(+), 10 deletions(-)
diff --git a/libc/test/src/sys/stat/CMakeLists.txt b/libc/test/src/sys/stat/CMakeLists.txt
index a0925810e404d..6eb963b133db0 100644
--- a/libc/test/src/sys/stat/CMakeLists.txt
+++ b/libc/test/src/sys/stat/CMakeLists.txt
@@ -162,7 +162,9 @@ add_libc_test(
DEPENDS
libc.hdr.fcntl_macros
libc.hdr.sys_stat_macros
+ libc.hdr.types.mode_t
libc.hdr.types.struct_stat
+ libc.src.__support.CPP.scope
libc.src.errno.errno
libc.src.sys.stat.fstatat
libc.src.fcntl.open
diff --git a/libc/test/src/sys/stat/fstatat_test.cpp b/libc/test/src/sys/stat/fstatat_test.cpp
index 0e3991ffb16b7..969102e6ebb41 100644
--- a/libc/test/src/sys/stat/fstatat_test.cpp
+++ b/libc/test/src/sys/stat/fstatat_test.cpp
@@ -8,7 +8,9 @@
#include "hdr/fcntl_macros.h"
#include "hdr/sys_stat_macros.h"
+#include "hdr/types/mode_t.h"
#include "hdr/types/struct_stat.h"
+#include "src/__support/CPP/scope.h"
#include "src/fcntl/open.h"
#include "src/sys/stat/fstatat.h"
#include "src/unistd/close.h"
@@ -26,15 +28,15 @@ TEST_F(LlvmLibcFstatatTest, StatWithAtFdcwd) {
int fd = LIBC_NAMESPACE::open(TEST_FILE, O_CREAT | O_WRONLY, S_IRWXU);
ASSERT_GT(fd, 0);
ASSERT_ERRNO_SUCCESS();
+ LIBC_NAMESPACE::cpp::scope_exit cleanup(
+ [&] { EXPECT_THAT(LIBC_NAMESPACE::unlink(TEST_FILE), Succeeds(0)); });
ASSERT_THAT(LIBC_NAMESPACE::close(fd), Succeeds(0));
struct stat statbuf;
ASSERT_THAT(LIBC_NAMESPACE::fstatat(AT_FDCWD, TEST_FILE, &statbuf, 0),
Succeeds(0));
- ASSERT_EQ(int(statbuf.st_mode), int(S_IRWXU | S_IFREG));
-
- ASSERT_THAT(LIBC_NAMESPACE::unlink(TEST_FILE), Succeeds(0));
+ ASSERT_EQ(statbuf.st_mode, static_cast<mode_t>(S_IRWXU | S_IFREG));
}
TEST_F(LlvmLibcFstatatTest, StatWithDirFd) {
@@ -45,20 +47,21 @@ TEST_F(LlvmLibcFstatatTest, StatWithDirFd) {
int fd = LIBC_NAMESPACE::open(TEST_FILE, O_CREAT | O_WRONLY, S_IRWXU);
ASSERT_GT(fd, 0);
ASSERT_ERRNO_SUCCESS();
+ LIBC_NAMESPACE::cpp::scope_exit cleanup_file(
+ [&] { EXPECT_THAT(LIBC_NAMESPACE::unlink(TEST_FILE), Succeeds(0)); });
ASSERT_THAT(LIBC_NAMESPACE::close(fd), Succeeds(0));
int dirfd = LIBC_NAMESPACE::open(TEST_DIR, O_DIRECTORY);
ASSERT_GT(dirfd, 0);
ASSERT_ERRNO_SUCCESS();
+ LIBC_NAMESPACE::cpp::scope_exit cleanup_dir(
+ [&] { EXPECT_THAT(LIBC_NAMESPACE::close(dirfd), Succeeds(0)); });
struct stat statbuf;
ASSERT_THAT(LIBC_NAMESPACE::fstatat(dirfd, TEST_FILE_BASENAME, &statbuf, 0),
Succeeds(0));
ASSERT_EQ(statbuf.st_mode, static_cast<mode_t>(S_IRWXU | S_IFREG));
-
- ASSERT_THAT(LIBC_NAMESPACE::close(dirfd), Succeeds(0));
- ASSERT_THAT(LIBC_NAMESPACE::unlink(TEST_FILE), Succeeds(0));
}
TEST_F(LlvmLibcFstatatTest, StatEmptyPath) {
@@ -67,15 +70,16 @@ TEST_F(LlvmLibcFstatatTest, StatEmptyPath) {
int fd = LIBC_NAMESPACE::open(TEST_FILE, O_CREAT | O_WRONLY, S_IRWXU);
ASSERT_GT(fd, 0);
ASSERT_ERRNO_SUCCESS();
+ LIBC_NAMESPACE::cpp::scope_exit cleanup([&] {
+ EXPECT_THAT(LIBC_NAMESPACE::close(fd), Succeeds(0));
+ EXPECT_THAT(LIBC_NAMESPACE::unlink(TEST_FILE), Succeeds(0));
+ });
struct stat statbuf;
ASSERT_THAT(LIBC_NAMESPACE::fstatat(fd, "", &statbuf, AT_EMPTY_PATH),
Succeeds(0));
- ASSERT_EQ(int(statbuf.st_mode), int(S_IRWXU | S_IFREG));
-
- ASSERT_THAT(LIBC_NAMESPACE::close(fd), Succeeds(0));
- ASSERT_THAT(LIBC_NAMESPACE::unlink(TEST_FILE), Succeeds(0));
+ ASSERT_EQ(statbuf.st_mode, static_cast<mode_t>(S_IRWXU | S_IFREG));
}
TEST_F(LlvmLibcFstatatTest, NonExistentFile) {
More information about the libc-commits
mailing list