[06/59] Sync mktime.c from Gnulib 2024-10-04

Message ID 20250105055750.1668721-7-eggert@cs.ucla.edu (mailing list archive)
State New
Headers
Series time: sync mktime from Gnulib |

Checks

Context Check Description
redhat-pt-bot/TryBot-apply_patch success Patch applied to master at the time it was sent

Commit Message

Paul Eggert Jan. 5, 2025, 5:56 a.m. UTC
  Refactor by syncing from Gnulib commit
8e0c8d148117086894fe26576d9255ea7f047a6d (Oct 4 21:07:09 2024 -0700),
which now uses C23's stdckdint.h for integer overflow checking,
uses static_assert instead of verify, uses a convert_time function
that is closer to what glibc wants, does not try to match tm_isdst in
timezones that never observe DST, and fixes a minor bug when timegm
fails on 32-bit time_t platforms.
* time/mktime-internal.h, time/mktime.c, time/timegm.c: Copy from Gnulib.
---
 time/mktime-internal.h |   9 ++-
 time/mktime.c          | 124 ++++++++++++++++++++++-------------------
 time/timegm.c          |   3 +-
 3 files changed, 73 insertions(+), 63 deletions(-)
  

Patch

diff --git a/time/mktime-internal.h b/time/mktime-internal.h
index 5fbcc0ec60..1da98b4373 100644
--- a/time/mktime-internal.h
+++ b/time/mktime-internal.h
@@ -71,9 +71,8 @@  typedef int mktime_offset_t;
 #endif
 
 /* Subroutine of mktime.  Return the time_t representation of TP and
-   normalize TP, given that a struct tm * maps to a time_t as performed
-   by FUNC.  Record next guess for localtime-gmtime offset in *OFFSET.  */
-extern __time64_t __mktime_internal (struct tm *tp,
-                                     struct tm *(*func) (__time64_t const *,
-                                                         struct tm *),
+   normalize TP, given that a struct tm * maps to a time_t.  If
+   LOCAL, the mapping is performed by localtime_r, otherwise by gmtime_r.
+   Record next guess for localtime-gmtime offset in *OFFSET.  */
+extern __time64_t __mktime_internal (struct tm *tp, bool local,
                                      mktime_offset_t *offset) attribute_hidden;
diff --git a/time/mktime.c b/time/mktime.c
index 86d496ca65..7f7d33492b 100644
--- a/time/mktime.c
+++ b/time/mktime.c
@@ -46,11 +46,11 @@ 
 #include <errno.h>
 #include <limits.h>
 #include <stdbool.h>
+#include <stdckdint.h>
 #include <stdlib.h>
 #include <string.h>
 
 #include <intprops.h>
-#include <verify.h>
 
 #ifndef NEED_MKTIME_INTERNAL
 # define NEED_MKTIME_INTERNAL 0
@@ -118,12 +118,12 @@  my_tzset (void)
    __time64_t values that mktime can generate even on platforms where
    __time64_t is wider than the int components of struct tm.  */
 
-#if INT_MAX <= LONG_MAX / 4 / 366 / 24 / 60 / 60
+# if INT_MAX <= LONG_MAX / 4 / 366 / 24 / 60 / 60
 typedef long int long_int;
-#else
+# else
 typedef long long int long_int;
-#endif
-verify (INT_MAX <= TYPE_MAXIMUM (long_int) / 4 / 366 / 24 / 60 / 60);
+# endif
+static_assert (INT_MAX <= TYPE_MAXIMUM (long_int) / 4 / 366 / 24 / 60 / 60);
 
 /* Shift A right by B bits portably, by dividing A by 2**B and
    truncating towards minus infinity.  B should be in the range 0 <= B
@@ -154,9 +154,9 @@  static long_int const mktime_max
   = (TYPE_MAXIMUM (long_int) < TYPE_MAXIMUM (__time64_t)
      ? TYPE_MAXIMUM (long_int) : TYPE_MAXIMUM (__time64_t));
 
-#define EPOCH_YEAR 1970
-#define TM_YEAR_BASE 1900
-verify (TM_YEAR_BASE % 100 == 0);
+# define EPOCH_YEAR 1970
+# define TM_YEAR_BASE 1900
+static_assert (TM_YEAR_BASE % 100 == 0);
 
 /* Is YEAR + TM_YEAR_BASE a leap year?  */
 static bool
@@ -171,9 +171,9 @@  leapyear (long_int year)
 }
 
 /* How many days come before each month (0-12).  */
-#ifndef _LIBC
+# ifndef _LIBC
 static
-#endif
+# endif
 const unsigned short int __mon_yday[2][13] =
   {
     /* Normal years.  */
@@ -205,7 +205,7 @@  static long_int
 ydhms_diff (long_int year1, long_int yday1, int hour1, int min1, int sec1,
 	    int year0, int yday0, int hour0, int min0, int sec0)
 {
-  verify (-1 / 2 == 0);
+  static_assert (-1 / 2 == 0);
 
   /* Compute intervening leap days correctly even if year is negative.
      Take care to avoid integer overflow here.  */
@@ -250,29 +250,33 @@  tm_diff (long_int year, long_int yday, int hour, int min, int sec,
 		     tp->tm_hour, tp->tm_min, tp->tm_sec);
 }
 
-/* Use CONVERT to convert T to a struct tm value in *TM.  T must be in
-   range for __time64_t.  Return TM if successful, NULL (setting errno) on
-   failure.  */
+/* Convert T to a struct tm value in *TM.  Use localtime64_r if LOCAL,
+   otherwise gmtime64_r.  T must be in range for __time64_t.  Return
+   TM if successful, NULL (setting errno) on failure.  */
 static struct tm *
-convert_time (struct tm *(*convert) (const __time64_t *, struct tm *),
-	      long_int t, struct tm *tm)
+convert_time (long_int t, bool local, struct tm *tm)
 {
   __time64_t x = t;
-  return convert (&x, tm);
+  if (local)
+    return __localtime64_r (&x, tm);
+  else
+    return __gmtime64_r (&x, tm);
 }
-
-/* Use CONVERT to convert *T to a broken down time in *TP.
-   If *T is out of range for conversion, adjust it so that
-   it is the nearest in-range value and then convert that.
-   A value is in range if it fits in both __time64_t and long_int.
-   Return TP on success, NULL (setting errno) on failure.  */
+/* Call it __tzconvert to sync with other parts of glibc.  */
+#define __tz_convert convert_time
+
+/* Convert *T to a broken down time in *TP (as if by localtime if
+   LOCAL, otherwise as if by gmtime).  If *T is out of range for
+   conversion, adjust it so that it is the nearest in-range value and
+   then convert that.  A value is in range if it fits in both
+   __time64_t and long_int.  Return TP on success, NULL (setting
+   errno) on failure.  */
 static struct tm *
-ranged_convert (struct tm *(*convert) (const __time64_t *, struct tm *),
-		long_int *t, struct tm *tp)
+ranged_convert (bool local, long_int *t, struct tm *tp)
 {
   long_int t1 = (*t < mktime_min ? mktime_min
 		 : *t <= mktime_max ? *t : mktime_max);
-  struct tm *r = convert_time (convert, t1, tp);
+  struct tm *r = __tz_convert (t1, local, tp);
   if (r)
     {
       *t = t1;
@@ -293,7 +297,7 @@  ranged_convert (struct tm *(*convert) (const __time64_t *, struct tm *),
       long_int mid = long_int_avg (ok, bad);
       if (mid == ok || mid == bad)
 	break;
-      if (convert_time (convert, mid, tp))
+      if (__tz_convert (mid, local, tp))
 	ok = mid, oktm = *tp;
       else if (errno != EOVERFLOW)
 	return NULL;
@@ -309,36 +313,45 @@  ranged_convert (struct tm *(*convert) (const __time64_t *, struct tm *),
 }
 
 
-/* Convert *TP to a __time64_t value, inverting
-   the monotonic and mostly-unit-linear conversion function CONVERT.
-   Use *OFFSET to keep track of a guess at the offset of the result,
+/* Convert *TP to a __time64_t value.  If LOCAL, the reverse mapping
+   is performed as if localtime, otherwise as if by gmtime.  Use
+   *OFFSET to keep track of a guess at the offset of the result,
    compared to what the result would be for UTC without leap seconds.
-   If *OFFSET's guess is correct, only one CONVERT call is needed.
-   If successful, set *TP to the canonicalized struct tm;
+   If *OFFSET's guess is correct, only one reverse mapping call is
+   needed.  If successful, set *TP to the canonicalized struct tm;
    otherwise leave *TP alone, return ((time_t) -1) and set errno.
    This function is external because it is used also by timegm.c.  */
 __time64_t
-__mktime_internal (struct tm *tp,
-		   struct tm *(*convert) (const __time64_t *, struct tm *),
-		   mktime_offset_t *offset)
+__mktime_internal (struct tm *tp, bool local, mktime_offset_t *offset)
 {
   struct tm tm;
 
-  /* The maximum number of probes (calls to CONVERT) should be enough
-     to handle any combinations of time zone rule changes, solar time,
-     leap seconds, and oscillations around a spring-forward gap.
-     POSIX.1 prohibits leap seconds, but some hosts have them anyway.  */
+  /* The maximum number of probes should be enough to handle any
+     combinations of time zone rule changes, solar time, leap seconds,
+     and oscillations around a spring-forward gap.  POSIX.1 prohibits
+     leap seconds, but some hosts have them anyway.  */
   int remaining_probes = 6;
 
-  /* Time requested.  Copy it in case CONVERT modifies *TP; this can
-     occur if TP is localtime's returned value and CONVERT is localtime.  */
+#ifndef _LIBC
+  /* Gnulib mktime doesn't lock the tz state, so it may need to probe
+     more often if some other thread changes local time while
+     __mktime_internal is probing.  Double the number of probes; this
+     should suffice for practical cases that are at all likely.  */
+  remaining_probes *= 2;
+#endif
+
+  /* Time requested.  Copy it in case gmtime/localtime modify *TP;
+     this can occur if TP is localtime's returned value and CONVERT is
+     localtime.  */
   int sec = tp->tm_sec;
   int min = tp->tm_min;
   int hour = tp->tm_hour;
   int mday = tp->tm_mday;
   int mon = tp->tm_mon;
   int year_requested = tp->tm_year;
-  int isdst = tp->tm_isdst;
+
+  /* If the timezone never observes DST, ignore any tm_isdst request.  */
+  int isdst = local && __daylight ? tp->tm_isdst : 0;
 
   /* 1 if the previous probe was DST.  */
   int dst2 = 0;
@@ -379,7 +392,7 @@  __mktime_internal (struct tm *tp,
   /* Invert CONVERT by probing.  First assume the same offset as last
      time.  */
 
-  INT_SUBTRACT_WRAPV (0, off, &negative_offset_guess);
+  ckd_sub (&negative_offset_guess, 0, off);
   long_int t0 = ydhms_diff (year, yday, hour, min, sec,
 			    EPOCH_YEAR - TM_YEAR_BASE, 0, 0, 0,
 			    negative_offset_guess);
@@ -389,7 +402,7 @@  __mktime_internal (struct tm *tp,
 
   while (true)
     {
-      if (! ranged_convert (convert, &t, &tm))
+      if (! ranged_convert (local, &t, &tm))
 	return -1;
       long_int dt = tm_diff (year, yday, hour, min, sec, &tm);
       if (dt == 0)
@@ -465,10 +478,10 @@  __mktime_internal (struct tm *tp,
 	for (direction = -1; direction <= 1; direction += 2)
 	  {
 	    long_int ot;
-	    if (! INT_ADD_WRAPV (t, delta * direction, &ot))
+	    if (! ckd_add (&ot, t, delta * direction))
 	      {
 		struct tm otm;
-		if (! ranged_convert (convert, &ot, &otm))
+		if (! ranged_convert (local, &ot, &otm))
 		  return -1;
 		if (! isdst_differ (isdst, otm.tm_isdst))
 		  {
@@ -478,7 +491,7 @@  __mktime_internal (struct tm *tp,
 						&otm);
 		    if (mktime_min <= gt && gt <= mktime_max)
 		      {
-			if (convert_time (convert, gt, &tm))
+			if (__tz_convert (gt, local, &tm))
 			  {
 			    t = gt;
 			    goto offset_found;
@@ -492,7 +505,7 @@  __mktime_internal (struct tm *tp,
 
       /* No unusual DST offset was found nearby.  Assume one-hour DST.  */
       t += 60 * 60 * dst_difference;
-      if (mktime_min <= t && t <= mktime_max && convert_time (convert, t, &tm))
+      if (mktime_min <= t && t <= mktime_max && __tz_convert (t, local, &tm))
 	goto offset_found;
 
       __set_errno (EOVERFLOW);
@@ -503,8 +516,8 @@  __mktime_internal (struct tm *tp,
   /* Set *OFFSET to the low-order bits of T - T0 - NEGATIVE_OFFSET_GUESS.
      This is just a heuristic to speed up the next mktime call, and
      correctness is unaffected if integer overflow occurs here.  */
-  INT_SUBTRACT_WRAPV (t, t0, offset);
-  INT_SUBTRACT_WRAPV (*offset, negative_offset_guess, offset);
+  ckd_sub (offset, t, t0);
+  ckd_sub (offset, *offset, negative_offset_guess);
 
   if (LEAP_SECONDS_POSSIBLE && sec_requested != tm.tm_sec)
     {
@@ -513,13 +526,13 @@  __mktime_internal (struct tm *tp,
       long_int sec_adjustment = sec == 0 && tm.tm_sec == 60;
       sec_adjustment -= sec;
       sec_adjustment += sec_requested;
-      if (INT_ADD_WRAPV (t, sec_adjustment, &t)
+      if (ckd_add (&t, t, sec_adjustment)
 	  || ! (mktime_min <= t && t <= mktime_max))
 	{
 	  __set_errno (EOVERFLOW);
 	  return -1;
 	}
-      if (! convert_time (convert, t, &tm))
+      if (! __tz_convert (t, local, &tm))
 	return -1;
     }
 
@@ -535,14 +548,13 @@  __mktime_internal (struct tm *tp,
 __time64_t
 __mktime64 (struct tm *tp)
 {
-  /* POSIX.1 8.1.1 requires that whenever mktime() is called, the
-     time zone abbreviations contained in the external variable 'tzname' shall
-     be set as if the tzset() function had been called.  */
+  /* POSIX.1 requires mktime to set external variables like 'tzname'
+     as though tzset had been called.  */
   __tzset ();
 
 # if defined _LIBC || NEED_MKTIME_WORKING
   static mktime_offset_t localtime_offset;
-  return __mktime_internal (tp, __localtime64_r, &localtime_offset);
+  return __mktime_internal (tp, true, &localtime_offset);
 # else
 #  undef mktime
   return mktime (tp);
diff --git a/time/timegm.c b/time/timegm.c
index 5e5fa0127f..4c2615b9fa 100644
--- a/time/timegm.c
+++ b/time/timegm.c
@@ -30,8 +30,7 @@  __time64_t
 __timegm64 (struct tm *tmp)
 {
   static mktime_offset_t gmtime_offset;
-  tmp->tm_isdst = 0;
-  return __mktime_internal (tmp, __gmtime64_r, &gmtime_offset);
+  return __mktime_internal (tmp, false, &gmtime_offset);
 }
 
 #if defined _LIBC && __TIMESIZE != 64