[libc-commits] [libc] [libc] Remove 32-bit time_t support (PR #200426)

Jeff Bailey via libc-commits libc-commits at lists.llvm.org
Mon Jun 1 05:06:47 PDT 2026


https://github.com/kaladron updated https://github.com/llvm/llvm-project/pull/200426

>From 150bf5556d74c57e35ea0b25a29ee0eac9537f55 Mon Sep 17 00:00:00 2001
From: Jeff Bailey <jbailey at raspberryginger.com>
Date: Fri, 29 May 2026 13:17:53 +0100
Subject: [PATCH 1/2] [libc] Remove 32-bit time_t support

Removed time_t_32.h and forced time_t to 64-bit (__INT64_TYPE__) on all
platforms.

Forced tv_nsec in struct timespec and tv_usec in struct timeval to
64-bit. This ensures struct timespec matches the kernel layout of
struct __kernel_timespec, allowing direct casting in syscall wrappers.
Note that under C23, tv_nsec is allowed to be any signed integer type
capable of representing [0, 999999999], making this 64-bit definition
fully compliant.

Updated syscall wrappers to prefer 64-bit time syscalls:
* clock_gettime64
* clock_settime64
* clock_nanosleep_time64
* pselect6_time64
* utimensat_time64
* futex_time64

For getitimer and setitimer (which lack kernel-level _time64 syscalls
on 32-bit), restored the 32-bit fallback conversion blocks.
Hardened both implementations with null-pointer crash protection,
and added a safe EOVERFLOW check in setitimer to prevent silent Y2038
truncation of relative timeouts exceeding 32-bit limits.

Added static_asserts to legacy fallback paths to prevent compiling on
32-bit platforms that lack 64-bit time support.

Removed obsolete Y2038 overflow tests from mktime and other time tests.

Assisted-by: Automated tooling, human reviewed.
---
 .../modules/LLVMLibCCompileOptionRules.cmake  |  4 -
 libc/cmake/modules/LLVMLibCFlagRules.cmake    | 13 ---
 libc/config/config.json                       |  6 --
 libc/include/llvm-libc-types/CMakeLists.txt   |  6 +-
 .../include/llvm-libc-types/struct_timespec.h |  5 +-
 libc/include/llvm-libc-types/suseconds_t.h    |  2 +-
 libc/include/llvm-libc-types/time_t.h         |  4 -
 libc/include/llvm-libc-types/time_t_32.h      | 20 -----
 .../OSUtil/linux/syscall_wrappers/utimensat.h | 10 ++-
 .../src/__support/threads/linux/futex_utils.h |  9 ++
 libc/src/__support/threads/linux/futex_word.h |  6 +-
 .../__support/time/linux/clock_gettime.cpp    | 33 ++++---
 .../__support/time/linux/clock_settime.cpp    | 23 ++---
 libc/src/poll/linux/poll.cpp                  | 15 ++--
 .../src/sched/linux/sched_rr_get_interval.cpp | 31 ++-----
 libc/src/sys/select/linux/select.cpp          | 15 ++--
 libc/src/sys/time/linux/getitimer.cpp         | 20 +++--
 libc/src/sys/time/linux/setitimer.cpp         | 30 +++++--
 libc/src/sys/time/linux/utimes.cpp            | 16 +---
 libc/src/time/linux/nanosleep.cpp             | 14 +--
 libc/src/time/time_constants.h                |  6 --
 libc/src/time/time_utils.cpp                  | 35 +-------
 libc/test/src/time/asctime_test.cpp           |  2 -
 libc/test/src/time/gmtime_r_test.cpp          |  2 -
 libc/test/src/time/gmtime_test.cpp            |  4 -
 libc/test/src/time/mktime_test.cpp            | 90 -------------------
 26 files changed, 128 insertions(+), 293 deletions(-)
 delete mode 100644 libc/include/llvm-libc-types/time_t_32.h

diff --git a/libc/cmake/modules/LLVMLibCCompileOptionRules.cmake b/libc/cmake/modules/LLVMLibCCompileOptionRules.cmake
index c65fc4db605c9..02921973e4940 100644
--- a/libc/cmake/modules/LLVMLibCCompileOptionRules.cmake
+++ b/libc/cmake/modules/LLVMLibCCompileOptionRules.cmake
@@ -134,10 +134,6 @@ function(_get_compile_options_from_config output_var)
     libc_add_definition(config_options "LIBC_COPT_USE_MEM_BUILTINS")
   endif()
 
-  if(LIBC_TYPES_TIME_T_IS_32_BIT AND LLVM_LIBC_FULL_BUILD)
-    libc_add_definition(config_options "LIBC_TYPES_TIME_T_IS_32_BIT")
-  endif()
-
   if(LIBC_ADD_NULL_CHECKS)
     libc_add_definition(config_options "LIBC_ADD_NULL_CHECKS")
   endif()
diff --git a/libc/cmake/modules/LLVMLibCFlagRules.cmake b/libc/cmake/modules/LLVMLibCFlagRules.cmake
index d721756f9b4c5..70a3f9b64483e 100644
--- a/libc/cmake/modules/LLVMLibCFlagRules.cmake
+++ b/libc/cmake/modules/LLVMLibCFlagRules.cmake
@@ -308,16 +308,3 @@ if(NOT DEFINED SKIP_FLAG_EXPANSION_ROUND_OPT)
     set(SKIP_FLAG_EXPANSION_ROUND_OPT TRUE)
   endif()
 endif()
-
-# Choose whether time_t is 32- or 64-bit, based on target architecture
-# and config options. This will be used to set a #define during the
-# library build, and also to select the right version of time_t.h for
-# the output headers.
-if(LIBC_TARGET_ARCHITECTURE_IS_ARM AND NOT (LIBC_CONF_TIME_64BIT))
-  # Set time_t to 32 bit for compatibility with glibc, unless
-  # configuration says otherwise
-  set(LIBC_TYPES_TIME_T_IS_32_BIT TRUE)
-else()
-  # Other platforms default to 64-bit time_t
-  set(LIBC_TYPES_TIME_T_IS_32_BIT FALSE)
-endif()
diff --git a/libc/config/config.json b/libc/config/config.json
index 27d9f08ed31e0..510a6c1b1990c 100644
--- a/libc/config/config.json
+++ b/libc/config/config.json
@@ -167,12 +167,6 @@
       "doc": "Make setjmp save the value of x18, and longjmp restore it. The AArch64 ABI delegates this register to platform ABIs, which can choose whether to make it caller-saved."
     }
   },
-  "time": {
-    "LIBC_CONF_TIME_64BIT": {
-      "value": false,
-      "doc": "Force the size of time_t to 64 bits, even on platforms where compatibility considerations would otherwise make it 32-bit."
-    }
-  },
   "general": {
     "LIBC_ADD_NULL_CHECKS": {
       "value": true,
diff --git a/libc/include/llvm-libc-types/CMakeLists.txt b/libc/include/llvm-libc-types/CMakeLists.txt
index 7c88e6e538879..6f3eca7e8e606 100644
--- a/libc/include/llvm-libc-types/CMakeLists.txt
+++ b/libc/include/llvm-libc-types/CMakeLists.txt
@@ -87,11 +87,7 @@ add_header(pthread_spinlock_t HDR pthread_spinlock_t.h DEPENDS .pid_t)
 add_header(pthread_t HDR pthread_t.h DEPENDS .__thread_type)
 add_header(pthread_id_np_t HDR pthread_id_np_t.h DEPENDS libc.include.llvm-libc-macros.stdint_macros)
 add_header(rlim_t HDR rlim_t.h)
-if(LIBC_TYPES_TIME_T_IS_32_BIT)
-  add_header(time_t HDR time_t_32.h DEST_HDR time_t.h)
-else()
-  add_header(time_t HDR time_t_64.h DEST_HDR time_t.h)
-endif()
+add_header(time_t HDR time_t_64.h DEST_HDR time_t.h)
 add_header(sighandler_t HDR sighandler_t.h)
 add_header(stack_t HDR stack_t.h DEPENDS .size_t)
 add_header(suseconds_t HDR suseconds_t.h)
diff --git a/libc/include/llvm-libc-types/struct_timespec.h b/libc/include/llvm-libc-types/struct_timespec.h
index 8993ecc7db8f0..7546795665dec 100644
--- a/libc/include/llvm-libc-types/struct_timespec.h
+++ b/libc/include/llvm-libc-types/struct_timespec.h
@@ -18,8 +18,9 @@
 
 struct timespec {
   time_t tv_sec; /* Seconds.  */
-  /* TODO: BIG_ENDIAN may require padding. */
-  long tv_nsec; /* Nanoseconds.  */
+  /* Nanoseconds. Forced to 64-bit to match __kernel_timespec layout (C23
+   * compliant). */
+  __INT64_TYPE__ tv_nsec;
 };
 #endif // __APPLE__
 
diff --git a/libc/include/llvm-libc-types/suseconds_t.h b/libc/include/llvm-libc-types/suseconds_t.h
index acc1822cb59e1..cd18b4f7cb889 100644
--- a/libc/include/llvm-libc-types/suseconds_t.h
+++ b/libc/include/llvm-libc-types/suseconds_t.h
@@ -19,7 +19,7 @@
 // to ensure type compatibility and avoid redefinition errors.
 #include <sys/_types/_suseconds_t.h>
 #else
-typedef long suseconds_t;
+typedef __INT64_TYPE__ suseconds_t;
 #endif // __APPLE__
 
 #endif // LLVM_LIBC_TYPES_SUSECONDS_T_H
diff --git a/libc/include/llvm-libc-types/time_t.h b/libc/include/llvm-libc-types/time_t.h
index 76920dc07ec69..7609a7a5c33f7 100644
--- a/libc/include/llvm-libc-types/time_t.h
+++ b/libc/include/llvm-libc-types/time_t.h
@@ -9,10 +9,6 @@
 #ifndef LLVM_LIBC_TYPES_TIME_T_H
 #define LLVM_LIBC_TYPES_TIME_T_H
 
-#ifdef LIBC_TYPES_TIME_T_IS_32_BIT
-#include "time_t_32.h"
-#else
 #include "time_t_64.h"
-#endif
 
 #endif // LLVM_LIBC_TYPES_TIME_T_H
diff --git a/libc/include/llvm-libc-types/time_t_32.h b/libc/include/llvm-libc-types/time_t_32.h
deleted file mode 100644
index 8d7a81e5ce7f7..0000000000000
--- a/libc/include/llvm-libc-types/time_t_32.h
+++ /dev/null
@@ -1,20 +0,0 @@
-//===-- Definition of the type time_t -------------------------------------===//
-//
-// 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_TYPES_TIME_T_32_H
-#define LLVM_LIBC_TYPES_TIME_T_32_H
-
-#if defined(__APPLE__)
-// Darwin provides its own definition for time_t. Include it directly
-// to ensure type compatibility and avoid redefinition errors.
-#include <sys/_types/_time_t.h>
-#else
-typedef __INT32_TYPE__ time_t;
-#endif // __APPLE__
-
-#endif // LLVM_LIBC_TYPES_TIME_T_32_H
diff --git a/libc/src/__support/OSUtil/linux/syscall_wrappers/utimensat.h b/libc/src/__support/OSUtil/linux/syscall_wrappers/utimensat.h
index d71afb9f5251d..cbf79f5de2cbf 100644
--- a/libc/src/__support/OSUtil/linux/syscall_wrappers/utimensat.h
+++ b/libc/src/__support/OSUtil/linux/syscall_wrappers/utimensat.h
@@ -23,14 +23,18 @@ 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;
+  int ret = syscall_impl<int>(SYS_utimensat_time64, dirfd, path, times, flags);
 #elif defined(SYS_utimensat)
-  constexpr auto UTIMENSAT_SYSCALL_ID = SYS_utimensat;
+  static_assert(
+      sizeof(timespec::tv_nsec) == sizeof(long),
+      "This legacy syscall fallback is only safe on platforms where tv_nsec "
+      "matches the register size (long). It is unsafe on 32-bit platforms "
+      "with 64-bit tv_nsec.");
+  int ret = syscall_impl<int>(SYS_utimensat, dirfd, path, times, flags);
 #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;
diff --git a/libc/src/__support/threads/linux/futex_utils.h b/libc/src/__support/threads/linux/futex_utils.h
index 4a39c66c6eb9a..ff6b5d526a3c1 100644
--- a/libc/src/__support/threads/linux/futex_utils.h
+++ b/libc/src/__support/threads/linux/futex_utils.h
@@ -22,6 +22,15 @@
 #include <linux/futex.h>
 
 namespace LIBC_NAMESPACE_DECL {
+
+#if !defined(SYS_futex_time64) && defined(SYS_futex)
+static_assert(
+    sizeof(timespec::tv_nsec) == sizeof(long),
+    "This legacy syscall fallback is only safe on platforms where tv_nsec "
+    "matches the register size (long). It is unsafe on 32-bit platforms "
+    "with 64-bit tv_nsec.");
+#endif
+
 class Futex : public cpp::Atomic<FutexWordType> {
 public:
   using Timeout = internal::AbsTimeout;
diff --git a/libc/src/__support/threads/linux/futex_word.h b/libc/src/__support/threads/linux/futex_word.h
index 1cf7befea5c88..9ef55ceaeb790 100644
--- a/libc/src/__support/threads/linux/futex_word.h
+++ b/libc/src/__support/threads/linux/futex_word.h
@@ -17,10 +17,10 @@ namespace LIBC_NAMESPACE_DECL {
 // Futexes are 32 bits in size on all platforms, including 64-bit platforms.
 using FutexWordType = uint32_t;
 
-#if SYS_futex
-constexpr auto FUTEX_SYSCALL_ID = SYS_futex;
-#elif defined(SYS_futex_time64)
+#if defined(SYS_futex_time64)
 constexpr auto FUTEX_SYSCALL_ID = SYS_futex_time64;
+#elif defined(SYS_futex)
+constexpr auto FUTEX_SYSCALL_ID = SYS_futex;
 #else
 #error "futex and futex_time64 syscalls not available."
 #endif
diff --git a/libc/src/__support/time/linux/clock_gettime.cpp b/libc/src/__support/time/linux/clock_gettime.cpp
index 944fc0a2b80fe..c893bcf9b772e 100644
--- a/libc/src/__support/time/linux/clock_gettime.cpp
+++ b/libc/src/__support/time/linux/clock_gettime.cpp
@@ -25,7 +25,21 @@ namespace internal {
 ErrorOr<int> clock_gettime(clockid_t clockid, timespec *ts) {
   using namespace vdso;
   int ret;
-#if defined(SYS_clock_gettime)
+#if defined(SYS_clock_gettime64)
+  TypedSymbol<VDSOSym::ClockGetTime64> clock_gettime64;
+  if (LIBC_LIKELY(clock_gettime64 != nullptr)) {
+    ret = clock_gettime64(clockid, reinterpret_cast<__kernel_timespec *>(ts));
+  } else {
+    ret = LIBC_NAMESPACE::syscall_impl<int>(SYS_clock_gettime64,
+                                            static_cast<long>(clockid),
+                                            reinterpret_cast<long>(ts));
+  }
+#elif defined(SYS_clock_gettime)
+  static_assert(
+      sizeof(timespec::tv_nsec) == sizeof(long),
+      "This legacy syscall fallback is only safe on platforms where tv_nsec "
+      "matches the register size (long). It is unsafe on 32-bit platforms "
+      "with 64-bit tv_nsec.");
   TypedSymbol<VDSOSym::ClockGetTime> clock_gettime;
   if (LIBC_LIKELY(clock_gettime != nullptr))
     ret = clock_gettime(clockid, ts);
@@ -33,23 +47,6 @@ ErrorOr<int> clock_gettime(clockid_t clockid, timespec *ts) {
     ret = LIBC_NAMESPACE::syscall_impl<int>(SYS_clock_gettime,
                                             static_cast<long>(clockid),
                                             reinterpret_cast<long>(ts));
-#elif defined(SYS_clock_gettime64)
-  static_assert(
-      sizeof(time_t) == sizeof(int64_t),
-      "SYS_clock_gettime64 requires struct timespec with 64-bit members.");
-
-  TypedSymbol<VDSOSym::ClockGetTime64> clock_gettime64;
-  __kernel_timespec ts64{};
-  if (LIBC_LIKELY(clock_gettime64 != nullptr))
-    ret = clock_gettime64(clockid, &ts64);
-  else
-    ret = LIBC_NAMESPACE::syscall_impl<int>(SYS_clock_gettime64,
-                                            static_cast<long>(clockid),
-                                            reinterpret_cast<long>(&ts64));
-  if (ret == 0) {
-    ts->tv_sec = static_cast<decltype(ts->tv_sec)>(ts64.tv_sec);
-    ts->tv_nsec = static_cast<decltype(ts->tv_nsec)>(ts64.tv_nsec);
-  }
 #else
 #error "SYS_clock_gettime and SYS_clock_gettime64 syscalls not available."
 #endif
diff --git a/libc/src/__support/time/linux/clock_settime.cpp b/libc/src/__support/time/linux/clock_settime.cpp
index dd42610adb031..ec96828b42352 100644
--- a/libc/src/__support/time/linux/clock_settime.cpp
+++ b/libc/src/__support/time/linux/clock_settime.cpp
@@ -23,24 +23,19 @@ namespace LIBC_NAMESPACE_DECL {
 namespace internal {
 ErrorOr<int> clock_settime(clockid_t clockid, const timespec *ts) {
   int ret;
-#if defined(SYS_clock_settime)
-  ret = LIBC_NAMESPACE::syscall_impl<int>(SYS_clock_settime,
+#if defined(SYS_clock_settime64)
+  ret = LIBC_NAMESPACE::syscall_impl<int>(SYS_clock_settime64,
                                           static_cast<long>(clockid),
                                           reinterpret_cast<long>(ts));
-#elif defined(SYS_clock_settime64)
+#elif defined(SYS_clock_settime)
   static_assert(
-      sizeof(time_t) == sizeof(int64_t),
-      "SYS_clock_settime64 requires struct timespec with 64-bit members.");
-
-  __kernel_timespec ts64{};
-
-  // Populate the 64-bit kernel structure from the user-provided timespec
-  ts64.tv_sec = static_cast<decltype(ts64.tv_sec)>(ts->tv_sec);
-  ts64.tv_nsec = static_cast<decltype(ts64.tv_nsec)>(ts->tv_nsec);
-
-  ret = LIBC_NAMESPACE::syscall_impl<int>(SYS_clock_settime64,
+      sizeof(timespec::tv_nsec) == sizeof(long),
+      "This legacy syscall fallback is only safe on platforms where tv_nsec "
+      "matches the register size (long). It is unsafe on 32-bit platforms "
+      "with 64-bit tv_nsec.");
+  ret = LIBC_NAMESPACE::syscall_impl<int>(SYS_clock_settime,
                                           static_cast<long>(clockid),
-                                          reinterpret_cast<long>(&ts64));
+                                          reinterpret_cast<long>(ts));
 #else
 #error "SYS_clock_settime and SYS_clock_settime64 syscalls not available."
 #endif
diff --git a/libc/src/poll/linux/poll.cpp b/libc/src/poll/linux/poll.cpp
index 4cac75b9687c8..7db5b16b1f5b9 100644
--- a/libc/src/poll/linux/poll.cpp
+++ b/libc/src/poll/linux/poll.cpp
@@ -16,7 +16,7 @@
 #include "src/__support/libc_errno.h"
 #include "src/__support/macros/config.h"
 
-#include <sys/syscall.h> // SYS_poll, SYS_ppoll
+#include <sys/syscall.h> // SYS_poll, SYS_ppoll, SYS_ppoll_time64
 
 namespace LIBC_NAMESPACE_DECL {
 
@@ -34,12 +34,17 @@ LLVM_LIBC_FUNCTION(int, poll, (pollfd * fds, nfds_t nfds, int timeout)) {
   } else {
     tsp = nullptr;
   }
-#if defined(SYS_ppoll)
-  ret =
-      LIBC_NAMESPACE::syscall_impl<int>(SYS_ppoll, fds, nfds, tsp, nullptr, 0);
-#elif defined(SYS_ppoll_time64)
+#if defined(SYS_ppoll_time64)
   ret = LIBC_NAMESPACE::syscall_impl<int>(SYS_ppoll_time64, fds, nfds, tsp,
                                           nullptr, 0);
+#elif defined(SYS_ppoll)
+  static_assert(
+      sizeof(timespec::tv_nsec) == sizeof(long),
+      "This legacy syscall fallback is only safe on platforms where tv_nsec "
+      "matches the register size (long). It is unsafe on 32-bit platforms "
+      "with 64-bit tv_nsec.");
+  ret =
+      LIBC_NAMESPACE::syscall_impl<int>(SYS_ppoll, fds, nfds, tsp, nullptr, 0);
 #else
 #error "poll, ppoll, ppoll_time64 syscalls not available."
 #endif // defined(SYS_ppoll) || defined(SYS_ppoll_time64)
diff --git a/libc/src/sched/linux/sched_rr_get_interval.cpp b/libc/src/sched/linux/sched_rr_get_interval.cpp
index eecbaa4dc03ce..c87d1808bc902 100644
--- a/libc/src/sched/linux/sched_rr_get_interval.cpp
+++ b/libc/src/sched/linux/sched_rr_get_interval.cpp
@@ -17,34 +17,21 @@
 #include "hdr/types/struct_timespec.h"
 #include <sys/syscall.h> // For syscall numbers.
 
-#ifdef SYS_sched_rr_get_interval_time64
-#include <linux/time_types.h> // For __kernel_timespec.
-#endif
-
 namespace LIBC_NAMESPACE_DECL {
 
 LLVM_LIBC_FUNCTION(int, sched_rr_get_interval,
                    (pid_t tid, struct timespec *tp)) {
-#ifdef SYS_sched_rr_get_interval
+#if defined(SYS_sched_rr_get_interval_time64)
+  int ret = LIBC_NAMESPACE::syscall_impl<int>(SYS_sched_rr_get_interval_time64,
+                                              tid, tp);
+#elif defined(SYS_sched_rr_get_interval)
+  static_assert(
+      sizeof(timespec::tv_nsec) == sizeof(long),
+      "This legacy syscall fallback is only safe on platforms where tv_nsec "
+      "matches the register size (long). It is unsafe on 32-bit platforms "
+      "with 64-bit tv_nsec.");
   int ret =
       LIBC_NAMESPACE::syscall_impl<int>(SYS_sched_rr_get_interval, tid, tp);
-#elif defined(SYS_sched_rr_get_interval_time64)
-  // The difference between the  and SYS_sched_rr_get_interval
-  // SYS_sched_rr_get_interval_time64 syscalls is the data type used for the
-  // time interval parameter: the latter takes a struct __kernel_timespec
-  int ret;
-  if (tp) {
-    struct __kernel_timespec ts32;
-    ret = LIBC_NAMESPACE::syscall_impl<int>(SYS_sched_rr_get_interval_time64,
-                                            tid, &ts32);
-    if (ret == 0) {
-      tp->tv_sec = ts32.tv_sec;
-      tp->tv_nsec = static_cast<long int>(ts32.tv_nsec);
-    }
-  } else
-    // When tp is a nullptr, we still do the syscall to set ret and errno
-    ret = LIBC_NAMESPACE::syscall_impl<int>(SYS_sched_rr_get_interval_time64,
-                                            tid, nullptr);
 #else
 #error                                                                         \
     "sched_rr_get_interval and sched_rr_get_interval_time64 syscalls not available."
diff --git a/libc/src/sys/select/linux/select.cpp b/libc/src/sys/select/linux/select.cpp
index 6c434eb584596..939cad4cd2bdd 100644
--- a/libc/src/sys/select/linux/select.cpp
+++ b/libc/src/sys/select/linux/select.cpp
@@ -16,7 +16,7 @@
 #include "src/__support/libc_errno.h"
 #include "src/__support/macros/config.h"
 
-#include <stddef.h>      // For size_t
+#include "hdr/types/size_t.h"
 #include <sys/syscall.h> // For syscall numbers.
 
 namespace LIBC_NAMESPACE_DECL {
@@ -54,12 +54,17 @@ LLVM_LIBC_FUNCTION(int, select,
     }
   }
   pselect6_sigset_t pss{nullptr, sizeof(sigset_t)};
-#if SYS_pselect6
-  int ret = LIBC_NAMESPACE::syscall_impl<int>(SYS_pselect6, nfds, read_set,
-                                              write_set, error_set, &ts, &pss);
-#elif defined(SYS_pselect6_time64)
+#if defined(SYS_pselect6_time64)
   int ret = LIBC_NAMESPACE::syscall_impl<int>(
       SYS_pselect6_time64, nfds, read_set, write_set, error_set, &ts, &pss);
+#elif defined(SYS_pselect6)
+  static_assert(
+      sizeof(timespec::tv_nsec) == sizeof(long),
+      "This legacy syscall fallback is only safe on platforms where tv_nsec "
+      "matches the register size (long). It is unsafe on 32-bit platforms "
+      "with 64-bit tv_nsec.");
+  int ret = LIBC_NAMESPACE::syscall_impl<int>(SYS_pselect6, nfds, read_set,
+                                              write_set, error_set, &ts, &pss);
 #else
 #error "SYS_pselect6 and SYS_pselect6_time64 syscalls not available."
 #endif
diff --git a/libc/src/sys/time/linux/getitimer.cpp b/libc/src/sys/time/linux/getitimer.cpp
index b874066796940..2a40491bc95fb 100644
--- a/libc/src/sys/time/linux/getitimer.cpp
+++ b/libc/src/sys/time/linux/getitimer.cpp
@@ -19,14 +19,18 @@ LLVM_LIBC_FUNCTION(int, getitimer, (int which, struct itimerval *curr_value)) {
   long ret = 0;
   if constexpr (sizeof(time_t) > sizeof(long)) {
     // There is no SYS_getitimer_time64 call, so we can't use time_t directly.
-    long curr_value32[4];
-    ret =
-        LIBC_NAMESPACE::syscall_impl<long>(SYS_getitimer, which, curr_value32);
-    if (!ret) {
-      curr_value->it_interval.tv_sec = curr_value32[0];
-      curr_value->it_interval.tv_usec = curr_value32[1];
-      curr_value->it_value.tv_sec = curr_value32[2];
-      curr_value->it_value.tv_usec = curr_value32[3];
+    if (curr_value) {
+      long curr_value32[4];
+      ret = LIBC_NAMESPACE::syscall_impl<long>(SYS_getitimer, which,
+                                               curr_value32);
+      if (!ret) {
+        curr_value->it_interval.tv_sec = curr_value32[0];
+        curr_value->it_interval.tv_usec = curr_value32[1];
+        curr_value->it_value.tv_sec = curr_value32[2];
+        curr_value->it_value.tv_usec = curr_value32[3];
+      }
+    } else {
+      ret = LIBC_NAMESPACE::syscall_impl<long>(SYS_getitimer, which, nullptr);
     }
   } else {
     ret = LIBC_NAMESPACE::syscall_impl<long>(SYS_getitimer, which, curr_value);
diff --git a/libc/src/sys/time/linux/setitimer.cpp b/libc/src/sys/time/linux/setitimer.cpp
index fb163586e30d9..2c2f0d7198204 100644
--- a/libc/src/sys/time/linux/setitimer.cpp
+++ b/libc/src/sys/time/linux/setitimer.cpp
@@ -6,7 +6,9 @@
 //
 //===----------------------------------------------------------------------===//
 #include "src/sys/time/setitimer.h"
+#include "hdr/errno_macros.h"
 #include "hdr/types/struct_itimerval.h"
+#include "src/__support/CPP/limits.h"
 #include "src/__support/OSUtil/syscall.h"
 #include "src/__support/common.h"
 #include "src/__support/libc_errno.h"
@@ -21,14 +23,30 @@ LLVM_LIBC_FUNCTION(int, setitimer,
   if constexpr (sizeof(time_t) > sizeof(long)) {
     // There is no SYS_setitimer_time64 call, so we can't use time_t directly,
     // and need to convert it to long first.
-    long new_value32[4] = {static_cast<long>(new_value->it_interval.tv_sec),
-                           static_cast<long>(new_value->it_interval.tv_usec),
-                           static_cast<long>(new_value->it_value.tv_sec),
-                           static_cast<long>(new_value->it_value.tv_usec)};
     long old_value32[4];
+    long *old_value32_ptr = old_value ? old_value32 : nullptr;
 
-    ret = LIBC_NAMESPACE::syscall_impl<long>(SYS_setitimer, which, new_value32,
-                                             old_value32);
+    if (new_value) {
+      // Check for overflow before casting to 32-bit long.
+      if (new_value->it_interval.tv_sec > cpp::numeric_limits<long>::max() ||
+          new_value->it_interval.tv_sec < cpp::numeric_limits<long>::min() ||
+          new_value->it_value.tv_sec > cpp::numeric_limits<long>::max() ||
+          new_value->it_value.tv_sec < cpp::numeric_limits<long>::min()) {
+        libc_errno = EOVERFLOW;
+        return -1;
+      }
+
+      long new_value32[4] = {static_cast<long>(new_value->it_interval.tv_sec),
+                             static_cast<long>(new_value->it_interval.tv_usec),
+                             static_cast<long>(new_value->it_value.tv_sec),
+                             static_cast<long>(new_value->it_value.tv_usec)};
+
+      ret = LIBC_NAMESPACE::syscall_impl<long>(SYS_setitimer, which,
+                                               new_value32, old_value32_ptr);
+    } else {
+      ret = LIBC_NAMESPACE::syscall_impl<long>(SYS_setitimer, which, nullptr,
+                                               old_value32_ptr);
+    }
 
     if (!ret && old_value) {
       old_value->it_interval.tv_sec = old_value32[0];
diff --git a/libc/src/sys/time/linux/utimes.cpp b/libc/src/sys/time/linux/utimes.cpp
index 1603f70f8f4db..abb56183cf7c3 100644
--- a/libc/src/sys/time/linux/utimes.cpp
+++ b/libc/src/sys/time/linux/utimes.cpp
@@ -22,16 +22,7 @@ namespace LIBC_NAMESPACE_DECL {
 
 LLVM_LIBC_FUNCTION(int, utimes,
                    (const char *path, const struct timeval times[2])) {
-#ifdef SYS_utimes
-  // No need to define a timespec struct, use the syscall directly.
-  int ret = LIBC_NAMESPACE::syscall_impl<int>(SYS_utimes, path, times);
-
-  if (ret < 0) {
-    libc_errno = -ret;
-    return -1;
-  }
-  return 0;
-#elif defined(SYS_utimensat) || defined(SYS_utimensat_time64)
+#if defined(SYS_utimensat) || defined(SYS_utimensat_time64)
 
   // the utimensat syscall requires a timespec struct, not timeval.
   struct timespec ts[2];
@@ -73,9 +64,8 @@ LLVM_LIBC_FUNCTION(int, utimes,
   }
 
   return 0;
-
 #else
-#error "utimes, utimensat, utimensat_time64,  syscalls not available."
-#endif // SYS_utimensat
+#error "utimensat or utimensat_time64 syscalls not available."
+#endif // SYS_utimensat || SYS_utimensat_time64
 }
 } // namespace LIBC_NAMESPACE_DECL
diff --git a/libc/src/time/linux/nanosleep.cpp b/libc/src/time/linux/nanosleep.cpp
index a30b97de40492..15b119ead20c6 100644
--- a/libc/src/time/linux/nanosleep.cpp
+++ b/libc/src/time/linux/nanosleep.cpp
@@ -19,14 +19,16 @@
 namespace LIBC_NAMESPACE_DECL {
 
 LLVM_LIBC_FUNCTION(int, nanosleep, (const timespec *req, timespec *rem)) {
-#if SYS_nanosleep
-  int ret = LIBC_NAMESPACE::syscall_impl<int>(SYS_nanosleep, req, rem);
-#elif defined(SYS_clock_nanosleep_time64)
-  static_assert(
-      sizeof(time_t) == sizeof(int64_t),
-      "SYS_clock_gettime64 requires struct timespec with 64-bit members.");
+#if defined(SYS_clock_nanosleep_time64)
   int ret = LIBC_NAMESPACE::syscall_impl<int>(SYS_clock_nanosleep_time64,
                                               CLOCK_REALTIME, 0, req, rem);
+#elif defined(SYS_nanosleep)
+  static_assert(
+      sizeof(timespec::tv_nsec) == sizeof(long),
+      "This legacy syscall fallback is only safe on platforms where tv_nsec "
+      "matches the register size (long). It is unsafe on 32-bit platforms "
+      "with 64-bit tv_nsec.");
+  int ret = LIBC_NAMESPACE::syscall_impl<int>(SYS_nanosleep, req, rem);
 #else
 #error "SYS_nanosleep and SYS_clock_nanosleep_time64 syscalls not available."
 #endif
diff --git a/libc/src/time/time_constants.h b/libc/src/time/time_constants.h
index 32eb0a171f8d1..078c427990a64 100644
--- a/libc/src/time/time_constants.h
+++ b/libc/src/time/time_constants.h
@@ -85,12 +85,6 @@ constexpr int DAYS_PER100_YEARS =
     (DAYS_PER_NON_LEAP_YEAR * 100) + (100 / 4) - 1;
 constexpr int DAYS_PER4_YEARS = (DAYS_PER_NON_LEAP_YEAR * 4) + 1;
 
-// The latest time that can be represented in this form is 03:14:07 UTC on
-// Tuesday, 19 January 2038 (corresponding to 2,147,483,647 seconds since the
-// start of the epoch). This means that systems using a 32-bit time_t type are
-// susceptible to the Year 2038 problem.
-constexpr int END_OF32_BIT_EPOCH_YEAR = 2038;
-
 constexpr time_t OUT_OF_RANGE_RETURN_VALUE = -1;
 
 constexpr cpp::array<cpp::string_view, DAYS_PER_WEEK> WEEK_DAY_NAMES = {
diff --git a/libc/src/time/time_utils.cpp b/libc/src/time/time_utils.cpp
index 1d0daea6b321e..d749ff83019c5 100644
--- a/libc/src/time/time_utils.cpp
+++ b/libc/src/time/time_utils.cpp
@@ -22,29 +22,6 @@ cpp::optional<time_t> mktime_internal(const tm *tm_out) {
   // TODO(rtenneti); Handle leap seconds.
   int64_t tm_year_from_base = tm_out->tm_year + time_constants::TIME_YEAR_BASE;
 
-  // 32-bit end-of-the-world is 03:14:07 UTC on 19 January 2038.
-  if (sizeof(time_t) == 4 &&
-      tm_year_from_base >= time_constants::END_OF32_BIT_EPOCH_YEAR) {
-    if (tm_year_from_base > time_constants::END_OF32_BIT_EPOCH_YEAR)
-      return cpp::nullopt;
-    if (tm_out->tm_mon > 0)
-      return cpp::nullopt;
-    if (tm_out->tm_mday > 19)
-      return cpp::nullopt;
-    else if (tm_out->tm_mday == 19) {
-      if (tm_out->tm_hour > 3)
-        return cpp::nullopt;
-      else if (tm_out->tm_hour == 3) {
-        if (tm_out->tm_min > 14)
-          return cpp::nullopt;
-        else if (tm_out->tm_min == 14) {
-          if (tm_out->tm_sec > 7)
-            return cpp::nullopt;
-        }
-      }
-    }
-  }
-
   // Years are ints.  A 32-bit year will fit into a 64-bit time_t.
   // A 64-bit year will not.
   static_assert(
@@ -141,15 +118,11 @@ int64_t update_from_seconds(time_t total_seconds, tm *tm) {
                                      30,           31, 30, 31, 31, 29};
 
   constexpr time_t time_min =
-      (sizeof(time_t) == 4)
-          ? INT_MIN
-          : INT_MIN * static_cast<int64_t>(
-                          time_constants::NUMBER_OF_SECONDS_IN_LEAP_YEAR);
+      INT_MIN *
+      static_cast<int64_t>(time_constants::NUMBER_OF_SECONDS_IN_LEAP_YEAR);
   constexpr time_t time_max =
-      (sizeof(time_t) == 4)
-          ? INT_MAX
-          : INT_MAX * static_cast<int64_t>(
-                          time_constants::NUMBER_OF_SECONDS_IN_LEAP_YEAR);
+      INT_MAX *
+      static_cast<int64_t>(time_constants::NUMBER_OF_SECONDS_IN_LEAP_YEAR);
 
   if (total_seconds < time_min || total_seconds > time_max)
     return time_utils::out_of_range();
diff --git a/libc/test/src/time/asctime_test.cpp b/libc/test/src/time/asctime_test.cpp
index 2868bdec79a6c..7635fbb8dc62e 100644
--- a/libc/test/src/time/asctime_test.cpp
+++ b/libc/test/src/time/asctime_test.cpp
@@ -185,8 +185,6 @@ TEST_F(LlvmLibcAsctime, EndOf32BitEpochYear) {
 }
 
 TEST_F(LlvmLibcAsctime, Max64BitYear) {
-  if (sizeof(time_t) == 4)
-    return;
   // Mon Jan 1 12:50:50 2170 (200 years from 1970),
   struct tm tm_data;
   char *result;
diff --git a/libc/test/src/time/gmtime_r_test.cpp b/libc/test/src/time/gmtime_r_test.cpp
index 700333519f3bd..8026d37618868 100644
--- a/libc/test/src/time/gmtime_r_test.cpp
+++ b/libc/test/src/time/gmtime_r_test.cpp
@@ -38,8 +38,6 @@ TEST_F(LlvmLibcGmTimeR, EndOf32BitEpochYear) {
 }
 
 TEST_F(LlvmLibcGmTimeR, Max64BitYear) {
-  if (sizeof(time_t) == 4)
-    return;
   // Test for Tue Jan 1 12:50:50 in 2,147,483,647th year.
   time_t seconds = 67767976202043050;
   struct tm tm_data;
diff --git a/libc/test/src/time/gmtime_test.cpp b/libc/test/src/time/gmtime_test.cpp
index 1df768ac721fd..6e28e5987fde5 100644
--- a/libc/test/src/time/gmtime_test.cpp
+++ b/libc/test/src/time/gmtime_test.cpp
@@ -18,8 +18,6 @@
 using LlvmLibcGmTime = LIBC_NAMESPACE::testing::ErrnoCheckingTest;
 
 TEST_F(LlvmLibcGmTime, OutOfRange) {
-  if (sizeof(time_t) < sizeof(int64_t))
-    return;
   time_t seconds =
       1 +
       INT_MAX *
@@ -275,8 +273,6 @@ TEST_F(LlvmLibcGmTime, EndOf32BitEpochYear) {
 }
 
 TEST_F(LlvmLibcGmTime, Max64BitYear) {
-  if (sizeof(time_t) == 4)
-    return;
   // Mon Jan 1 12:50:50 2170 (200 years from 1970),
   time_t seconds = 6311479850;
   struct tm *tm_data = LIBC_NAMESPACE::gmtime(&seconds);
diff --git a/libc/test/src/time/mktime_test.cpp b/libc/test/src/time/mktime_test.cpp
index 97901a8a35a38..6927ec935b85a 100644
--- a/libc/test/src/time/mktime_test.cpp
+++ b/libc/test/src/time/mktime_test.cpp
@@ -227,94 +227,6 @@ TEST(LlvmLibcMkTime, InvalidYear) {
                tm_data);
 }
 
-TEST(LlvmLibcMkTime, InvalidEndOf32BitEpochYear) {
-  if (sizeof(time_t) != 4)
-    return;
-  {
-    // 2038-01-19 03:14:08 tests overflow of the second in 2038.
-    struct tm tm_data{.tm_sec = 8,
-                      .tm_min = 14,
-                      .tm_hour = 3,
-                      .tm_mday = 19,
-                      .tm_mon = Month::JANUARY,
-                      .tm_year = tm_year(2038),
-                      .tm_wday = 0,
-                      .tm_yday = 0,
-                      .tm_isdst = 0};
-    EXPECT_THAT(LIBC_NAMESPACE::mktime(&tm_data), Fails<time_t>(EOVERFLOW));
-  }
-
-  {
-    // 2038-01-19 03:15:07 tests overflow of the minute in 2038.
-    struct tm tm_data{.tm_sec = 7,
-                      .tm_min = 15,
-                      .tm_hour = 3,
-                      .tm_mday = 19,
-                      .tm_mon = Month::JANUARY,
-                      .tm_year = tm_year(2038),
-                      .tm_wday = 0,
-                      .tm_yday = 0,
-                      .tm_isdst = 0};
-    EXPECT_THAT(LIBC_NAMESPACE::mktime(&tm_data), Fails<time_t>(EOVERFLOW));
-  }
-
-  {
-    // 2038-01-19 04:14:07 tests overflow of the hour in 2038.
-    struct tm tm_data{.tm_sec = 7,
-                      .tm_min = 14,
-                      .tm_hour = 4,
-                      .tm_mday = 19,
-                      .tm_mon = Month::JANUARY,
-                      .tm_year = tm_year(2038),
-                      .tm_wday = 0,
-                      .tm_yday = 0,
-                      .tm_isdst = 0};
-    EXPECT_THAT(LIBC_NAMESPACE::mktime(&tm_data), Fails<time_t>(EOVERFLOW));
-  }
-
-  {
-    // 2038-01-20 03:14:07 tests overflow of the day in 2038.
-    struct tm tm_data{.tm_sec = 7,
-                      .tm_min = 14,
-                      .tm_hour = 3,
-                      .tm_mday = 20,
-                      .tm_mon = Month::JANUARY,
-                      .tm_year = tm_year(2038),
-                      .tm_wday = 0,
-                      .tm_yday = 0,
-                      .tm_isdst = 0};
-    EXPECT_THAT(LIBC_NAMESPACE::mktime(&tm_data), Fails<time_t>(EOVERFLOW));
-  }
-
-  {
-    // 2038-02-19 03:14:07 tests overflow of the month in 2038.
-    struct tm tm_data{.tm_sec = 7,
-                      .tm_min = 14,
-                      .tm_hour = 3,
-                      .tm_mday = 19,
-                      .tm_mon = Month::FEBRUARY,
-                      .tm_year = tm_year(2038),
-                      .tm_wday = 0,
-                      .tm_yday = 0,
-                      .tm_isdst = 0};
-    EXPECT_THAT(LIBC_NAMESPACE::mktime(&tm_data), Fails<time_t>(EOVERFLOW));
-  }
-
-  {
-    // 2039-01-19 03:14:07 tests overflow of the year.
-    struct tm tm_data{.tm_sec = 7,
-                      .tm_min = 14,
-                      .tm_hour = 3,
-                      .tm_mday = 19,
-                      .tm_mon = Month::JANUARY,
-                      .tm_year = tm_year(2039),
-                      .tm_wday = 0,
-                      .tm_yday = 0,
-                      .tm_isdst = 0};
-    EXPECT_THAT(LIBC_NAMESPACE::mktime(&tm_data), Fails<time_t>(EOVERFLOW));
-  }
-}
-
 TEST(LlvmLibcMkTime, InvalidMonths) {
   {
     // -1 month from 1970-01-01 00:00:00 returns 1969-12-01 00:00:00.
@@ -621,8 +533,6 @@ TEST(LlvmLibcMkTime, EndOf32BitEpochYear) {
 }
 
 TEST(LlvmLibcMkTime, Max64BitYear) {
-  if (sizeof(time_t) == 4)
-    return;
   {
     // Mon Jan 1 12:50:50 2170 (200 years from 1970),
     struct tm tm_data{.tm_sec = 50,

>From 23c8a5c05da4979356fb7909b91c69b3d36b946e Mon Sep 17 00:00:00 2001
From: Jeff Bailey <jbailey at raspberryginger.com>
Date: Mon, 1 Jun 2026 13:03:08 +0100
Subject: [PATCH 2/2] Fixes from Mikhail R. Gadelha for casting type

---
 libc/test/src/__support/time/linux/timeout_test.cpp |  3 ++-
 libc/test/src/sys/time/utimes_test.cpp              | 11 ++++++-----
 2 files changed, 8 insertions(+), 6 deletions(-)

diff --git a/libc/test/src/__support/time/linux/timeout_test.cpp b/libc/test/src/__support/time/linux/timeout_test.cpp
index 33e8231959926..8a59312f43529 100644
--- a/libc/test/src/__support/time/linux/timeout_test.cpp
+++ b/libc/test/src/__support/time/linux/timeout_test.cpp
@@ -45,7 +45,8 @@ TEST(LlvmLibcSupportLinuxTimeoutTest, NoChangeIfClockIsMonotonic) {
   ensure_monotonicity(*result);
   ASSERT_FALSE(result->is_realtime());
   ASSERT_EQ(result->get_timespec().tv_sec, static_cast<time_t>(10000));
-  ASSERT_EQ(result->get_timespec().tv_nsec, static_cast<long int>(0));
+  ASSERT_EQ(result->get_timespec().tv_nsec,
+            static_cast<decltype(result->get_timespec().tv_nsec)>(0));
 }
 TEST(LlvmLibcSupportLinuxTimeoutTest, ValidAfterConversion) {
   timespec ts;
diff --git a/libc/test/src/sys/time/utimes_test.cpp b/libc/test/src/sys/time/utimes_test.cpp
index 3c8688ebda8d9..685459ed9d1dc 100644
--- a/libc/test/src/sys/time/utimes_test.cpp
+++ b/libc/test/src/sys/time/utimes_test.cpp
@@ -53,11 +53,12 @@ TEST_F(LlvmLibcUtimesTest, ChangeTimesSpecific) {
   ASSERT_GT(statbuf.st_ctim.tv_sec, static_cast<time_t>(0));
 
   // microseconds
-  ASSERT_EQ(statbuf.st_atim.tv_nsec,
-            static_cast<long>(times[0].tv_usec * 1000));
-  ASSERT_EQ(statbuf.st_mtim.tv_nsec,
-            static_cast<long>(times[1].tv_usec * 1000));
-
+   ASSERT_EQ(statbuf.st_atim.tv_nsec,
+            static_cast<decltype(statbuf.st_atim.tv_nsec)>(times[0].tv_usec *
+                                                           1000));
+   ASSERT_EQ(statbuf.st_mtim.tv_nsec,
+            static_cast<decltype(statbuf.st_mtim.tv_nsec)>(times[1].tv_usec *
+       
   // legacy way to check seconds
   ASSERT_EQ(statbuf.st_atime, times[0].tv_sec);
   ASSERT_EQ(statbuf.st_mtime, times[1].tv_sec);



More information about the libc-commits mailing list