about summary refs log tree commit diff
path: root/time
diff options
context:
space:
mode:
authorPaul Eggert <eggert@cs.ucla.edu>2018-11-15 22:59:33 +0100
committerAlbert ARIBAUD (3ADEV) <albert.aribaud@3adev.fr>2018-11-15 22:59:33 +0100
commit86aece3bfbd44538ba4fdc947872c81d4c5e6e61 (patch)
tree1bf096dc1c3f3f671a48f2b8f8ef28ddf50c21d7 /time
parentf6b3331bbae638d1bb50813fceb429d3b3dc0eb9 (diff)
downloadglibc-86aece3bfbd44538ba4fdc947872c81d4c5e6e61.tar.gz
glibc-86aece3bfbd44538ba4fdc947872c81d4c5e6e61.tar.xz
glibc-86aece3bfbd44538ba4fdc947872c81d4c5e6e61.zip
mktime: fix non-EOVERFLOW errno handling
[BZ#23789]
mktime was not properly reporting failures when the underlying
localtime_r fails with errno != EOVERFLOW; it incorrectly treated
them like EOVERFLOW failures, and set errno to EOVERFLOW.
The problem could happen on non-glibc platforms, with Gnulib.
* time/mktime.c (guess_time_tm): Remove, replacing with ...
(tm_diff): ... this simpler function, which does not change errno.
All callers changed to deal with errno themselves.
(ranged_convert, __mktime_internal): Return failure immediately if
the underlying function reports any failure other than EOVERFLOW.
(__mktime_internal): Set errno to EOVERFLOW if the spring-forward
gap code fails.
Diffstat (limited to 'time')
-rw-r--r--time/mktime.c197
1 files changed, 99 insertions, 98 deletions
diff --git a/time/mktime.c b/time/mktime.c
index 6d5b8cf838..dc83985bbd 100644
--- a/time/mktime.c
+++ b/time/mktime.c
@@ -250,45 +250,25 @@ long_int_avg (long_int a, long_int b)
   return shr (a, 1) + shr (b, 1) + ((a | b) & 1);
 }
 
-/* Return a time_t value corresponding to (YEAR-YDAY HOUR:MIN:SEC),
-   assuming that T corresponds to *TP and that no clock adjustments
-   occurred between *TP and the desired time.
-   Although T and the returned value are of type long_int,
-   they represent time_t values and must be in time_t range.
-   If TP is null, return a value not equal to T; this avoids false matches.
+/* Return a long_int value corresponding to (YEAR-YDAY HOUR:MIN:SEC)
+   minus *TP seconds, assuming no clock adjustments occurred between
+   the two timestamps.
+
    YEAR and YDAY must not be so large that multiplying them by three times the
    number of seconds in a year (or day, respectively) would overflow long_int.
-   If TP is non-null and the returned value would be out of range, set
-   errno to EOVERFLOW and yield a minimal or maximal in-range value
-   that is not equal to T.  */
+   *TP should be in the usual range.  */
 static long_int
-guess_time_tm (long_int year, long_int yday, int hour, int min, int sec,
-	       long_int t, const struct tm *tp)
+tm_diff (long_int year, long_int yday, int hour, int min, int sec,
+	 struct tm const *tp)
 {
-  if (tp)
-    {
-      long_int result;
-      long_int d = ydhms_diff (year, yday, hour, min, sec,
-			       tp->tm_year, tp->tm_yday,
-			       tp->tm_hour, tp->tm_min, tp->tm_sec);
-      if (! INT_ADD_WRAPV (t, d, &result))
-	return result;
-      __set_errno (EOVERFLOW);
-    }
-
-  /* An error occurred, probably overflow.  Return the nearest result
-     that is actually in range, except don't report a zero difference
-     if the actual difference is nonzero, as that would cause a false
-     match; and don't oscillate between two values, as that would
-     confuse the spring-forward gap detector.  */
-  return (t < long_int_avg (mktime_min, mktime_max)
-	  ? (t <= mktime_min + 1 ? t + 1 : mktime_min)
-	  : (mktime_max - 1 <= t ? t - 1 : mktime_max));
+  return ydhms_diff (year, yday, hour, min, sec,
+		     tp->tm_year, tp->tm_yday,
+		     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 time_t.  Return TM if successful, NULL if T is out of
-   range for CONVERT.  */
+   range for time_t.  Return TM if successful, NULL (setting errno) on
+   failure.  */
 static struct tm *
 convert_time (struct tm *(*convert) (const time_t *, struct tm *),
 	      long_int t, struct tm *tm)
@@ -300,49 +280,48 @@ convert_time (struct tm *(*convert) (const time_t *, struct 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 time_t and long_int.  */
+   A value is in range if it fits in both time_t and long_int.
+   Return TP on success, NULL (setting errno) on failure.  */
 static struct tm *
 ranged_convert (struct tm *(*convert) (const time_t *, struct tm *),
 		long_int *t, struct tm *tp)
 {
-  struct tm *r;
-  if (*t < mktime_min)
-    *t = mktime_min;
-  else if (mktime_max < *t)
-    *t = mktime_max;
-  r = convert_time (convert, *t, tp);
-
-  if (!r && *t)
+  long_int t1 = (*t < mktime_min ? mktime_min
+		 : *t <= mktime_max ? *t : mktime_max);
+  struct tm *r = convert_time (convert, t1, tp);
+  if (r)
     {
-      long_int bad = *t;
-      long_int ok = 0;
-
-      /* BAD is a known unconvertible value, and OK is a known good one.
-	 Use binary search to narrow the range between BAD and OK until
-	 they differ by 1.  */
-      while (true)
-	{
-	  long_int mid = long_int_avg (ok, bad);
-	  if (mid == ok || mid == bad)
-	    break;
-	  r = convert_time (convert, mid, tp);
-	  if (r)
-	    ok = mid;
-	  else
-	    bad = mid;
-	}
+      *t = t1;
+      return r;
+    }
+  if (errno != EOVERFLOW)
+    return NULL;
 
-      *t = ok;
+  long_int bad = t1;
+  long_int ok = 0;
+  struct tm oktm; oktm.tm_sec = -1;
 
-      if (!r && ok)
-	{
-	  /* The last conversion attempt failed;
-	     revert to the most recent successful attempt.  */
-	  r = convert_time (convert, ok, tp);
-	}
+  /* BAD is a known out-of-range value, and OK is a known in-range one.
+     Use binary search to narrow the range between BAD and OK until
+     they differ by 1.  */
+  while (true)
+    {
+      long_int mid = long_int_avg (ok, bad);
+      if (mid == ok || mid == bad)
+	break;
+      if (convert_time (convert, mid, tp))
+	ok = mid, oktm = *tp;
+      else if (errno != EOVERFLOW)
+	return NULL;
+      else
+	bad = mid;
     }
 
-  return r;
+  if (oktm.tm_sec < 0)
+    return NULL;
+  *t = ok;
+  *tp = oktm;
+  return tp;
 }
 
 
@@ -359,7 +338,6 @@ __mktime_internal (struct tm *tp,
 		   struct tm *(*convert) (const time_t *, struct tm *),
 		   mktime_offset_t *offset)
 {
-  long_int t, gt, t0, t1, t2;
   struct tm tm;
 
   /* The maximum number of probes (calls to CONVERT) should be enough
@@ -379,7 +357,7 @@ __mktime_internal (struct tm *tp,
   int isdst = tp->tm_isdst;
 
   /* 1 if the previous probe was DST.  */
-  int dst2;
+  int dst2 = 0;
 
   /* Ensure that mon is in range, and set year accordingly.  */
   int mon_remainder = mon % 12;
@@ -418,36 +396,46 @@ __mktime_internal (struct tm *tp,
      time.  */
 
   INT_SUBTRACT_WRAPV (0, off, &negative_offset_guess);
-  t0 = ydhms_diff (year, yday, hour, min, sec,
-		   EPOCH_YEAR - TM_YEAR_BASE, 0, 0, 0, negative_offset_guess);
+  long_int t0 = ydhms_diff (year, yday, hour, min, sec,
+			    EPOCH_YEAR - TM_YEAR_BASE, 0, 0, 0,
+			    negative_offset_guess);
+  long_int t = t0, t1 = t0, t2 = t0;
 
   /* Repeatedly use the error to improve the guess.  */
 
-  for (t = t1 = t2 = t0, dst2 = 0;
-       (gt = guess_time_tm (year, yday, hour, min, sec, t,
-			    ranged_convert (convert, &t, &tm)),
-	t != gt);
-       t1 = t2, t2 = t, t = gt, dst2 = tm.tm_isdst != 0)
-    if (t == t1 && t != t2
-	&& (tm.tm_isdst < 0
-	    || (isdst < 0
-		? dst2 <= (tm.tm_isdst != 0)
-		: (isdst != 0) != (tm.tm_isdst != 0))))
-      /* We can't possibly find a match, as we are oscillating
-	 between two values.  The requested time probably falls
-	 within a spring-forward gap of size GT - T.  Follow the common
-	 practice in this case, which is to return a time that is GT - T
-	 away from the requested time, preferring a time whose
-	 tm_isdst differs from the requested value.  (If no tm_isdst
-	 was requested and only one of the two values has a nonzero
-	 tm_isdst, prefer that value.)  In practice, this is more
-	 useful than returning -1.  */
-      goto offset_found;
-    else if (--remaining_probes == 0)
-      {
-	__set_errno (EOVERFLOW);
+  while (true)
+    {
+      if (! ranged_convert (convert, &t, &tm))
 	return -1;
-      }
+      long_int dt = tm_diff (year, yday, hour, min, sec, &tm);
+      if (dt == 0)
+	break;
+
+      if (t == t1 && t != t2
+	  && (tm.tm_isdst < 0
+	      || (isdst < 0
+		  ? dst2 <= (tm.tm_isdst != 0)
+		  : (isdst != 0) != (tm.tm_isdst != 0))))
+	/* We can't possibly find a match, as we are oscillating
+	   between two values.  The requested time probably falls
+	   within a spring-forward gap of size DT.  Follow the common
+	   practice in this case, which is to return a time that is DT
+	   away from the requested time, preferring a time whose
+	   tm_isdst differs from the requested value.  (If no tm_isdst
+	   was requested and only one of the two values has a nonzero
+	   tm_isdst, prefer that value.)  In practice, this is more
+	   useful than returning -1.  */
+	goto offset_found;
+
+      remaining_probes--;
+      if (remaining_probes == 0)
+	{
+	  __set_errno (EOVERFLOW);
+	  return -1;
+	}
+
+      t1 = t2, t2 = t, t += dt, dst2 = tm.tm_isdst != 0;
+    }
 
   /* We have a match.  Check whether tm.tm_isdst has the requested
      value, if any.  */
@@ -489,17 +477,30 @@ __mktime_internal (struct tm *tp,
 	    if (! INT_ADD_WRAPV (t, delta * direction, &ot))
 	      {
 		struct tm otm;
-		ranged_convert (convert, &ot, &otm);
+		if (! ranged_convert (convert, &ot, &otm))
+		  return -1;
 		if (! isdst_differ (isdst, otm.tm_isdst))
 		  {
 		    /* We found the desired tm_isdst.
 		       Extrapolate back to the desired time.  */
-		    t = guess_time_tm (year, yday, hour, min, sec, ot, &otm);
-		    ranged_convert (convert, &t, &tm);
-		    goto offset_found;
+		    long_int gt = ot + tm_diff (year, yday, hour, min, sec,
+						&otm);
+		    if (mktime_min <= gt && gt <= mktime_max)
+		      {
+			if (convert_time (convert, gt, &tm))
+			  {
+			    t = gt;
+			    goto offset_found;
+			  }
+			if (errno != EOVERFLOW)
+			  return -1;
+		      }
 		  }
 	      }
 	  }
+
+      __set_errno (EOVERFLOW);
+      return -1;
     }
 
  offset_found: