[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