[libc-commits] [libc] 4c4c1db - [libc] Add utimensat syscall wrapper and entrypoint (#188347)

via libc-commits libc-commits at lists.llvm.org
Wed Mar 25 00:53:33 PDT 2026


Author: Jeff Bailey
Date: 2026-03-25T07:53:29Z
New Revision: 4c4c1db7c69a6fda6cfa6bc6066bb09a433edc89

URL: https://github.com/llvm/llvm-project/commit/4c4c1db7c69a6fda6cfa6bc6066bb09a433edc89
DIFF: https://github.com/llvm/llvm-project/commit/4c4c1db7c69a6fda6cfa6bc6066bb09a433edc89.diff

LOG: [libc] Add utimensat syscall wrapper and entrypoint (#188347)

Implemented the utimensat syscall for Linux and added the entrypoint to
sys/stat.h.

* Added utimensat syscall wrapper to OSUtil
* Updated utimes to use the utimensat wrapper
* Added utimensat unit tests to sys/stat
* Configured entrypoints for x86_64, riscv, and aarch64

Added: 
    libc/src/__support/OSUtil/linux/syscall_wrappers/utimensat.h
    libc/src/sys/stat/linux/utimensat.cpp
    libc/src/sys/stat/utimensat.h
    libc/test/src/sys/stat/utimensat_test.cpp

Modified: 
    libc/config/linux/aarch64/entrypoints.txt
    libc/config/linux/riscv/entrypoints.txt
    libc/config/linux/x86_64/entrypoints.txt
    libc/include/llvm-libc-macros/linux/sys-stat-macros.h
    libc/include/sys/stat.yaml
    libc/src/__support/OSUtil/linux/syscall_wrappers/CMakeLists.txt
    libc/src/sys/stat/CMakeLists.txt
    libc/src/sys/stat/linux/CMakeLists.txt
    libc/src/sys/time/linux/CMakeLists.txt
    libc/src/sys/time/linux/utimes.cpp
    libc/test/src/sys/stat/CMakeLists.txt

Removed: 
    


################################################################################
diff  --git a/libc/config/linux/aarch64/entrypoints.txt b/libc/config/linux/aarch64/entrypoints.txt
index d6569e59a3e5f..e7a452ec03ca4 100644
--- a/libc/config/linux/aarch64/entrypoints.txt
+++ b/libc/config/linux/aarch64/entrypoints.txt
@@ -289,6 +289,7 @@ set(TARGET_LIBC_ENTRYPOINTS
     libc.src.sys.stat.mkdir
     libc.src.sys.stat.mkdirat
     libc.src.sys.stat.stat
+    libc.src.sys.stat.utimensat
 
     # sys/statvfs.h
     libc.src.sys.statvfs.fstatvfs

diff  --git a/libc/config/linux/riscv/entrypoints.txt b/libc/config/linux/riscv/entrypoints.txt
index fdfce36b824f0..a102fc07a2c0d 100644
--- a/libc/config/linux/riscv/entrypoints.txt
+++ b/libc/config/linux/riscv/entrypoints.txt
@@ -289,12 +289,13 @@ set(TARGET_LIBC_ENTRYPOINTS
     libc.src.sys.stat.mkdir
     libc.src.sys.stat.mkdirat
     libc.src.sys.stat.stat
+    libc.src.sys.stat.utimensat
 
     # sys/statvfs.h
     libc.src.sys.statvfs.fstatvfs
     libc.src.sys.statvfs.statvfs
 
-    # sys/utimes.h entrypoints
+    # sys/time.h entrypoints
     libc.src.sys.time.utimes
 
     # sys/utsname.h entrypoints

diff  --git a/libc/config/linux/x86_64/entrypoints.txt b/libc/config/linux/x86_64/entrypoints.txt
index 8cceaccc314dc..af54217b190fc 100644
--- a/libc/config/linux/x86_64/entrypoints.txt
+++ b/libc/config/linux/x86_64/entrypoints.txt
@@ -305,12 +305,13 @@ set(TARGET_LIBC_ENTRYPOINTS
     libc.src.sys.stat.mkdir
     libc.src.sys.stat.mkdirat
     libc.src.sys.stat.stat
+    libc.src.sys.stat.utimensat
 
     # sys/statvfs.h
     libc.src.sys.statvfs.fstatvfs
     libc.src.sys.statvfs.statvfs
 
-    # sys/utimes.h entrypoints
+    # sys/time.h entrypoints
     libc.src.sys.time.utimes
 
     # sys/utsname.h entrypoints

diff  --git a/libc/include/llvm-libc-macros/linux/sys-stat-macros.h b/libc/include/llvm-libc-macros/linux/sys-stat-macros.h
index 3013121d0f3cb..046fa8f2313dd 100644
--- a/libc/include/llvm-libc-macros/linux/sys-stat-macros.h
+++ b/libc/include/llvm-libc-macros/linux/sys-stat-macros.h
@@ -45,4 +45,7 @@
 #define S_IWOTH 00002
 #define S_IXOTH 00001
 
+#define UTIME_NOW ((1L << 30) - 1L)
+#define UTIME_OMIT ((1L << 30) - 2L)
+
 #endif // LLVM_LIBC_MACROS_LINUX_SYS_STAT_MACROS_H

diff  --git a/libc/include/sys/stat.yaml b/libc/include/sys/stat.yaml
index 7f013420818ab..9017e19266e3a 100644
--- a/libc/include/sys/stat.yaml
+++ b/libc/include/sys/stat.yaml
@@ -76,3 +76,12 @@ functions:
     arguments:
       - type: const char *__restrict
       - type: struct stat *__restrict
+  - name: utimensat
+    standards:
+      - POSIX
+    return_type: int
+    arguments:
+      - type: int
+      - type: const char *
+      - type: const struct timespec *
+      - type: int

diff  --git a/libc/src/__support/OSUtil/linux/syscall_wrappers/CMakeLists.txt b/libc/src/__support/OSUtil/linux/syscall_wrappers/CMakeLists.txt
index 8bc266941347a..d5f552f7b6c46 100644
--- a/libc/src/__support/OSUtil/linux/syscall_wrappers/CMakeLists.txt
+++ b/libc/src/__support/OSUtil/linux/syscall_wrappers/CMakeLists.txt
@@ -62,3 +62,16 @@ add_header_library(
     libc.hdr.types.mode_t
     libc.include.sys_syscall
 )
+
+add_header_library(
+  utimensat
+  HDRS
+    utimensat.h
+  DEPENDS
+    libc.src.__support.OSUtil.osutil
+    libc.src.__support.common
+    libc.src.__support.error_or
+    libc.src.__support.macros.config
+    libc.hdr.types.struct_timespec
+    libc.include.sys_syscall
+)

diff  --git a/libc/src/__support/OSUtil/linux/syscall_wrappers/utimensat.h b/libc/src/__support/OSUtil/linux/syscall_wrappers/utimensat.h
new file mode 100644
index 0000000000000..d71afb9f5251d
--- /dev/null
+++ b/libc/src/__support/OSUtil/linux/syscall_wrappers/utimensat.h
@@ -0,0 +1,42 @@
+//===-- Implementation header for utimensat ---------------------*- 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___SUPPORT_OSUTIL_SYSCALL_WRAPPERS_UTIMENSAT_H
+#define LLVM_LIBC_SRC___SUPPORT_OSUTIL_SYSCALL_WRAPPERS_UTIMENSAT_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 "hdr/types/struct_timespec.h"
+#include <sys/syscall.h> // For syscall numbers
+
+namespace LIBC_NAMESPACE_DECL {
+namespace linux_syscalls {
+
+LIBC_INLINE ErrorOr<int> utimensat(int dirfd, const char *path,
+                                   const struct timespec times[2], int flags) {
+#if defined(SYS_utimensat_time64)
+  constexpr auto UTIMENSAT_SYSCALL_ID = SYS_utimensat_time64;
+#elif defined(SYS_utimensat)
+  constexpr auto UTIMENSAT_SYSCALL_ID = SYS_utimensat;
+#else
+#error "utimensat or utimensat_time64 syscalls not available."
+#endif
+
+  int ret = syscall_impl<int>(UTIMENSAT_SYSCALL_ID, dirfd, path, times, flags);
+  if (ret < 0)
+    return Error(-static_cast<int>(ret));
+  return ret;
+}
+
+} // namespace linux_syscalls
+} // namespace LIBC_NAMESPACE_DECL
+
+#endif // LLVM_LIBC_SRC___SUPPORT_OSUTIL_SYSCALL_WRAPPERS_UTIMENSAT_H

diff  --git a/libc/src/sys/stat/CMakeLists.txt b/libc/src/sys/stat/CMakeLists.txt
index 0775f882c6387..6bc8ad64bd0df 100644
--- a/libc/src/sys/stat/CMakeLists.txt
+++ b/libc/src/sys/stat/CMakeLists.txt
@@ -57,3 +57,10 @@ add_entrypoint_object(
   DEPENDS
     .${LIBC_TARGET_OS}.stat
 )
+
+add_entrypoint_object(
+  utimensat
+  ALIAS
+  DEPENDS
+    .${LIBC_TARGET_OS}.utimensat
+)

diff  --git a/libc/src/sys/stat/linux/CMakeLists.txt b/libc/src/sys/stat/linux/CMakeLists.txt
index c99872c3ad09e..4fd8fe98890de 100644
--- a/libc/src/sys/stat/linux/CMakeLists.txt
+++ b/libc/src/sys/stat/linux/CMakeLists.txt
@@ -118,3 +118,16 @@ add_entrypoint_object(
     libc.include.sys_stat
     libc.src.errno.errno
 )
+
+add_entrypoint_object(
+  utimensat
+  SRCS
+    utimensat.cpp
+  HDRS
+    ../utimensat.h
+  DEPENDS
+    libc.hdr.types.struct_timespec
+    libc.src.__support.OSUtil.linux.syscall_wrappers.utimensat
+    libc.src.__support.common
+    libc.src.__support.libc_errno
+)

diff  --git a/libc/src/sys/stat/linux/utimensat.cpp b/libc/src/sys/stat/linux/utimensat.cpp
new file mode 100644
index 0000000000000..17d3436ce3d23
--- /dev/null
+++ b/libc/src/sys/stat/linux/utimensat.cpp
@@ -0,0 +1,29 @@
+//===-- Linux implementation of utimensat ---------------------------------===//
+//
+// 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/utimensat.h"
+
+#include "src/__support/OSUtil/linux/syscall_wrappers/utimensat.h"
+#include "src/__support/common.h"
+#include "src/__support/libc_errno.h"
+#include "src/__support/macros/config.h"
+
+namespace LIBC_NAMESPACE_DECL {
+
+LLVM_LIBC_FUNCTION(int, utimensat,
+                   (int dirfd, const char *path, const struct timespec times[2],
+                    int flags)) {
+  auto result = linux_syscalls::utimensat(dirfd, path, times, flags);
+  if (!result.has_value()) {
+    libc_errno = result.error();
+    return -1;
+  }
+  return result.value();
+}
+
+} // namespace LIBC_NAMESPACE_DECL

diff  --git a/libc/src/sys/stat/utimensat.h b/libc/src/sys/stat/utimensat.h
new file mode 100644
index 0000000000000..8845beaf40e51
--- /dev/null
+++ b/libc/src/sys/stat/utimensat.h
@@ -0,0 +1,22 @@
+//===-- Implementation header for utimensat ---------------------*- 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_UTIMENSAT_H
+#define LLVM_LIBC_SRC_SYS_STAT_UTIMENSAT_H
+
+#include "hdr/types/struct_timespec.h"
+#include "src/__support/macros/config.h"
+
+namespace LIBC_NAMESPACE_DECL {
+
+int utimensat(int dirfd, const char *path, const struct timespec times[2],
+              int flags);
+
+} // namespace LIBC_NAMESPACE_DECL
+
+#endif // LLVM_LIBC_SRC_SYS_STAT_UTIMENSAT_H

diff  --git a/libc/src/sys/time/linux/CMakeLists.txt b/libc/src/sys/time/linux/CMakeLists.txt
index 7a4dd68eeac2c..60fbd14f73174 100644
--- a/libc/src/sys/time/linux/CMakeLists.txt
+++ b/libc/src/sys/time/linux/CMakeLists.txt
@@ -5,14 +5,15 @@ add_entrypoint_object(
   HDRS
     ../utimes.h
   DEPENDS
+    libc.hdr.types.struct_timespec
     libc.hdr.types.struct_timeval
     libc.hdr.fcntl_macros
     libc.src.__support.OSUtil.osutil
+    libc.src.__support.OSUtil.linux.syscall_wrappers.utimensat
     libc.include.sys_stat
     libc.include.sys_syscall
     libc.include.fcntl
-    libc.src.__support.OSUtil.osutil
-    libc.src.errno.errno
+    libc.src.__support.libc_errno
 )
 
 add_entrypoint_object(

diff  --git a/libc/src/sys/time/linux/utimes.cpp b/libc/src/sys/time/linux/utimes.cpp
index e740190bc8198..037094630b824 100644
--- a/libc/src/sys/time/linux/utimes.cpp
+++ b/libc/src/sys/time/linux/utimes.cpp
@@ -12,9 +12,8 @@
 #include "hdr/types/struct_timespec.h"
 #include "hdr/types/struct_timeval.h"
 
+#include "src/__support/OSUtil/linux/syscall_wrappers/utimensat.h"
 #include "src/__support/OSUtil/syscall.h"
-#include "src/__support/common.h"
-
 #include "src/__support/libc_errno.h"
 
 #include <sys/syscall.h>
@@ -28,13 +27,13 @@ LLVM_LIBC_FUNCTION(int, utimes,
 #ifdef SYS_utimes
   // No need to define a timespec struct, use the syscall directly.
   ret = LIBC_NAMESPACE::syscall_impl<int>(SYS_utimes, path, times);
-#elif defined(SYS_utimensat) || defined(SYS_utimensat_time64)
 
-#if defined(SYS_utimensat)
-  constexpr auto UTIMES_SYSCALL_ID = SYS_utimensat;
-#elif defined(SYS_utimensat_time64)
-  constexpr auto UTIMES_SYSCALL_ID = SYS_utimensat_time64;
-#endif
+  if (ret < 0) {
+    libc_errno = -ret;
+    return -1;
+  }
+  return 0;
+#elif defined(SYS_utimensat) || defined(SYS_utimensat_time64)
 
   // the utimensat syscall requires a timespec struct, not timeval.
   struct timespec ts[2];
@@ -68,19 +67,17 @@ LLVM_LIBC_FUNCTION(int, utimes,
   // as setting times to the current time.
 
   // utimensat syscall.
-  // flags=0 means don't follow symlinks (like utimes)
-  ret = LIBC_NAMESPACE::syscall_impl<int>(UTIMES_SYSCALL_ID, AT_FDCWD, path,
-                                          ts_ptr, 0);
-
-#else
-#error "utimes, utimensat, utimensat_time64,  syscalls not available."
-#endif // SYS_utimensat
-
-  if (ret < 0) {
-    libc_errno = -ret;
+  // flags=0 means follow symlinks (same as utimes)
+  auto result = linux_syscalls::utimensat(AT_FDCWD, path, ts_ptr, 0);
+  if (!result.has_value()) {
+    libc_errno = result.error();
     return -1;
   }
 
   return 0;
+
+#else
+#error "utimes, utimensat, utimensat_time64,  syscalls not available."
+#endif // SYS_utimensat
 }
 } // namespace LIBC_NAMESPACE_DECL

diff  --git a/libc/test/src/sys/stat/CMakeLists.txt b/libc/test/src/sys/stat/CMakeLists.txt
index 77ec265e16e3a..85fc7589bd81e 100644
--- a/libc/test/src/sys/stat/CMakeLists.txt
+++ b/libc/test/src/sys/stat/CMakeLists.txt
@@ -125,3 +125,24 @@ add_libc_unittest(
     libc.test.UnitTest.ErrnoCheckingTest
     libc.test.UnitTest.ErrnoSetterMatcher
 )
+
+add_libc_unittest(
+  utimensat_test
+  SUITE
+    libc_sys_stat_unittests
+  SRCS
+    utimensat_test.cpp
+  DEPENDS
+    libc.hdr.fcntl_macros
+    libc.hdr.sys_stat_macros
+    libc.hdr.types.struct_timespec
+    libc.include.sys_stat
+    libc.src.errno.errno
+    libc.src.sys.stat.utimensat
+    libc.src.fcntl.open
+    libc.src.sys.stat.stat
+    libc.src.unistd.close
+    libc.src.stdio.remove
+    libc.test.UnitTest.ErrnoCheckingTest
+    libc.test.UnitTest.ErrnoSetterMatcher
+)

diff  --git a/libc/test/src/sys/stat/utimensat_test.cpp b/libc/test/src/sys/stat/utimensat_test.cpp
new file mode 100644
index 0000000000000..e0753bf5043ce
--- /dev/null
+++ b/libc/test/src/sys/stat/utimensat_test.cpp
@@ -0,0 +1,119 @@
+//===-- Unittests for utimensat -------------------------------------------===//
+//
+// 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_timespec.h"
+#include "src/fcntl/open.h"
+#include "src/stdio/remove.h"
+#include "src/sys/stat/stat.h"
+#include "src/sys/stat/utimensat.h"
+#include "src/unistd/close.h"
+
+#include "test/UnitTest/ErrnoCheckingTest.h"
+#include "test/UnitTest/ErrnoSetterMatcher.h"
+#include "test/UnitTest/Test.h"
+
+using LlvmLibcUtimensatTest = LIBC_NAMESPACE::testing::ErrnoCheckingTest;
+
+// SUCCESS: Takes a file and successfully updates
+// its last access and modified times.
+TEST_F(LlvmLibcUtimensatTest, ChangeTimesSpecific) {
+  using LIBC_NAMESPACE::testing::ErrnoSetterMatcher::Succeeds;
+
+  constexpr const char *FILE_PATH = "utimensat_pass.test";
+  auto TEST_FILE = libc_make_test_file_path(FILE_PATH);
+  int fd = LIBC_NAMESPACE::open(TEST_FILE, O_WRONLY | O_CREAT, S_IRWXU);
+  ASSERT_ERRNO_SUCCESS();
+  ASSERT_GT(fd, 0);
+  ASSERT_THAT(LIBC_NAMESPACE::close(fd), Succeeds(0));
+
+  // make a dummy timespec struct
+  struct timespec times[2];
+  times[0].tv_sec = 54321;
+  times[0].tv_nsec = 12345000;
+  times[1].tv_sec = 43210;
+  times[1].tv_nsec = 23456000;
+
+  // ensure utimensat succeeds
+  ASSERT_THAT(LIBC_NAMESPACE::utimensat(AT_FDCWD, TEST_FILE, times, 0),
+              Succeeds(0));
+
+  // verify the times values against stat of the TEST_FILE
+  struct stat statbuf;
+  ASSERT_EQ(LIBC_NAMESPACE::stat(TEST_FILE, &statbuf), 0);
+
+  // seconds
+  ASSERT_EQ(statbuf.st_atim.tv_sec, times[0].tv_sec);
+  ASSERT_EQ(statbuf.st_mtim.tv_sec, times[1].tv_sec);
+
+  // nanoseconds
+  ASSERT_EQ(statbuf.st_atim.tv_nsec, times[0].tv_nsec);
+  ASSERT_EQ(statbuf.st_mtim.tv_nsec, times[1].tv_nsec);
+
+  ASSERT_THAT(LIBC_NAMESPACE::remove(TEST_FILE), Succeeds(0));
+}
+
+// FAILURE: Invalid values in the timespec struct
+// to check that utimensat rejects it.
+TEST_F(LlvmLibcUtimensatTest, InvalidNanoseconds) {
+  using LIBC_NAMESPACE::testing::ErrnoSetterMatcher::Fails;
+  using LIBC_NAMESPACE::testing::ErrnoSetterMatcher::Succeeds;
+
+  constexpr const char *FILE_PATH = "utimensat_fail.test";
+  auto TEST_FILE = libc_make_test_file_path(FILE_PATH);
+  int fd = LIBC_NAMESPACE::open(TEST_FILE, O_WRONLY | O_CREAT, S_IRWXU);
+  ASSERT_GT(fd, 0);
+  ASSERT_THAT(LIBC_NAMESPACE::close(fd), Succeeds(0));
+
+  // make a dummy timespec struct
+  // populated with bad nsec values
+  struct timespec times[2];
+  times[0].tv_sec = 54321;
+  times[0].tv_nsec = 4567;
+  times[1].tv_sec = 43210;
+  times[1].tv_nsec = 1000000000; // invalid
+
+  // ensure utimensat fails
+  ASSERT_THAT(LIBC_NAMESPACE::utimensat(AT_FDCWD, TEST_FILE, times, 0),
+              Fails(EINVAL));
+
+  // check for failure on
+  // the other possible bad values
+  times[0].tv_sec = 54321;
+  times[0].tv_nsec = -4567; // invalid
+  times[1].tv_sec = 43210;
+  times[1].tv_nsec = 1000;
+
+  // ensure utimensat fails once more
+  ASSERT_THAT(LIBC_NAMESPACE::utimensat(AT_FDCWD, TEST_FILE, times, 0),
+              Fails(EINVAL));
+  ASSERT_THAT(LIBC_NAMESPACE::remove(TEST_FILE), Succeeds(0));
+}
+
+// SUCCESS: Test UTIME_NOW and UTIME_OMIT macros
+TEST_F(LlvmLibcUtimensatTest, UtimeNowAndOmit) {
+  using LIBC_NAMESPACE::testing::ErrnoSetterMatcher::Succeeds;
+
+  constexpr const char *FILE_PATH = "utimensat_now.test";
+  auto TEST_FILE = libc_make_test_file_path(FILE_PATH);
+  int fd = LIBC_NAMESPACE::open(TEST_FILE, O_WRONLY | O_CREAT, S_IRWXU);
+  ASSERT_GT(fd, 0);
+  ASSERT_ERRNO_SUCCESS();
+  ASSERT_THAT(LIBC_NAMESPACE::close(fd), Succeeds(0));
+
+  struct timespec times[2];
+  times[0].tv_sec = 0;
+  times[0].tv_nsec = UTIME_NOW;
+  times[1].tv_sec = 0;
+  times[1].tv_nsec = UTIME_OMIT;
+
+  ASSERT_THAT(LIBC_NAMESPACE::utimensat(AT_FDCWD, TEST_FILE, times, 0),
+              Succeeds(0));
+  ASSERT_THAT(LIBC_NAMESPACE::remove(TEST_FILE), Succeeds(0));
+}


        


More information about the libc-commits mailing list