bug-gnulib
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[PATCH] parse-datetime: handle timezones reentrantly


From: Paul Eggert
Subject: [PATCH] parse-datetime: handle timezones reentrantly
Date: Fri, 20 Jan 2017 18:07:07 -0800

This API change was prompted by a report by Pádraig Brady in:
https://bug.debian.org/851934#10
To help fix the bug, make parse_datetime2 more reentrant.
* NEWS: Document this incompatible change.
* lib/parse-datetime.h, lib/parse-datetime.y (parse_datetime2):
Add two arguments, the timezone and the timezone name.
All callers changed.  If TZ="..." is specified, use it for
calculating defaults.
* lib/parse-datetime.y: Don't include xalloc.h or use xmalloc, as
this code should be usable in a library.
(mktime_ok, get_effective_timezone):
Accept timezone arg too.  All callers changed.
(get_tz): Remove.
(get_effective_timezone): Check for failures.
* modules/parse-datetime: Add time_r, time_rz.  Remove xalloc.
---
 ChangeLog              |  18 ++++
 NEWS                   |   4 +
 lib/parse-datetime.h   |   2 +-
 lib/parse-datetime.y   | 228 ++++++++++++++++++++++++-------------------------
 modules/parse-datetime |   3 +-
 5 files changed, 135 insertions(+), 120 deletions(-)

diff --git a/ChangeLog b/ChangeLog
index 41fbf4a..29d5954 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,21 @@
+2017-01-20  Paul Eggert  <address@hidden>
+
+       parse-datetime: handle timezones reentrantly
+       This API change was prompted by a report by Pádraig Brady in:
+       https://bug.debian.org/851934#10
+       To help fix the bug, make parse_datetime2 more reentrant.
+       * NEWS: Document this incompatible change.
+       * lib/parse-datetime.h, lib/parse-datetime.y (parse_datetime2):
+       Add two arguments, the timezone and the timezone name.
+       All callers changed.  If TZ="..." is specified, use it for
+       calculating defaults.
+       * lib/parse-datetime.y: Don't include xalloc.h or use xmalloc, as
+       this code should be usable in a library.
+       (mktime_ok, get_effective_timezone):
+       Accept timezone arg too.  All callers changed.
+       (get_tz): Remove.
+       (get_effective_timezone): Check for failures.
+
 2017-01-20  Eric Blake  <address@hidden>
 
        localename: port to cygwin 2.6
diff --git a/NEWS b/NEWS
index 90ce1a2..266275a 100644
--- a/NEWS
+++ b/NEWS
@@ -42,6 +42,10 @@ User visible incompatible changes
 
 Date        Modules         Changes
 
+2017-01-20  parse-datetime  The parse_datetime2 function now takes two
+                            more arguments TZ and TZSTRING, for the
+                            time zone and its name.
+
 2017-01-16  host-cpu-c-abi  On ARM platforms, HOST_CPU_C_ABI is now set to
                             'arm' or 'armhf' instead of 'armel'.
 
diff --git a/lib/parse-datetime.h b/lib/parse-datetime.h
index fa6a9a3..2be5faa 100644
--- a/lib/parse-datetime.h
+++ b/lib/parse-datetime.h
@@ -26,4 +26,4 @@ bool parse_datetime (struct timespec *, char const *, struct 
timespec const *);
 
 /* same as above, supporting additional flags */
 bool parse_datetime2 (struct timespec *, char const *, struct timespec const *,
-                      unsigned int flags);
+                      unsigned int flags, timezone_t, char const *);
diff --git a/lib/parse-datetime.y b/lib/parse-datetime.y
index c604be6..6af4f29 100644
--- a/lib/parse-datetime.y
+++ b/lib/parse-datetime.y
@@ -68,7 +68,6 @@
 #include <string.h>
 
 #include "gettext.h"
-#include "xalloc.h"
 
 #define _(str) gettext (str)
 
@@ -1531,19 +1530,21 @@ yyerror (parser_control const *pc _GL_UNUSED,
   return 0;
 }
 
-/* If *TM0 is the old and *TM1 is the new value of a struct tm after
-   passing it to mktime, return true if it's OK that mktime returned T.
-   It's not OK if *TM0 has out-of-range members.  */
+/* In timezone TZ, if *TM0 is the old and *TM1 is the new value of a
+   struct tm after passing it to mktime_z, return true if it's OK that
+   mktime_z returned T.  It's not OK if *TM0 has out-of-range
+   members.  */
 
 static bool
-mktime_ok (struct tm const *tm0, struct tm const *tm1, time_t t)
+mktime_ok (timezone_t tz, struct tm const *tm0, struct tm const *tm1, time_t t)
 {
+  struct tm ltm;
   if (t == (time_t) -1)
     {
       /* Guard against falsely reporting an error when parsing a
          timestamp that happens to equal (time_t) -1, on a host that
          supports such a timestamp.  */
-      tm1 = localtime (&t);
+      tm1 = localtime_rz (tz, &t, &ltm);
       if (!tm1)
         return false;
     }
@@ -1564,22 +1565,6 @@ enum { TZBUFSIZE = 100 };
    see days_to_name(), debug_strftime() and debug_mktime_not_ok() */
 enum { DBGBUFSIZE = 100 };
 
-/* Return a copy of TZ, stored in TZBUF if it fits, and heap-allocated
-   otherwise.  */
-static char *
-get_tz (char tzbuf[TZBUFSIZE])
-{
-  char *tz = getenv ("TZ");
-  if (tz)
-    {
-      size_t tzsize = strlen (tz) + 1;
-      tz = (tzsize <= TZBUFSIZE
-            ? memcpy (tzbuf, tz, tzsize)
-            : xmemdup (tz, tzsize));
-    }
-  return tz;
-}
-
 /* debugging: format a 'struct tm' into a buffer, taking the parser's
    timezone information into account (if pc!=NULL). */
 static const char*
@@ -1708,48 +1693,54 @@ debug_mktime_not_ok (struct tm const *tm0, struct tm 
const *tm1,
 
 /* Returns the effective local timezone, in minutes. */
 static long int
-get_effective_timezone (void)
+get_effective_timezone (timezone_t tz)
 {
-  /* TODO: check for failures */
-  const time_t z = 0;
-  time_t lz ;
-  struct tm *ltm;
-  ltm = localtime (&z);
-  lz = timegm (ltm)/60;
-  return (long int)lz;
+  time_t z = 0;
+  struct tm tm;
+  if (! localtime_rz (tz, &z, &tm))
+    return 0;
+  return timegm (&tm) / 60;
 }
 
-/* The original interface: run with debug=false */
+/* The original interface: run with debug=false and the default timezone.   */
 bool
 parse_datetime (struct timespec *result, char const *p,
                 struct timespec const *now)
 {
-  return parse_datetime2 (result, p, now, 0);
+  char const *tzstring = getenv ("TZ");
+  timezone_t tz = tzalloc (tzstring);
+  if (!tz)
+    return false;
+  bool ok = parse_datetime2 (result, p, now, 0, tz, tzstring);
+  tzfree (tz);
+  return ok;
 }
 
 /* Parse a date/time string, storing the resulting time value into *RESULT.
    The string itself is pointed to by P.  Return true if successful.
    P can be an incomplete or relative time specification; if so, use
-   *NOW as the basis for the returned time.  */
+   *NOW as the basis for the returned time.  Default to timezone
+   TZDEFAULT, which corresponds to tzalloc (TZSTRING).  */
 bool
 parse_datetime2 (struct timespec *result, char const *p,
-                 struct timespec const *now, unsigned int flags)
+                 struct timespec const *now, unsigned int flags,
+                 timezone_t tzdefault, char const *tzstring)
 {
   time_t Start;
   long int Start_ns;
-  struct tm const *tmp;
+  struct tm tmp;
   struct tm tm;
   struct tm tm0;
   parser_control pc;
   struct timespec gettime_buffer;
   unsigned char c;
-  bool tz_was_altered = false;
-  char *tz0 = NULL;
-  char tz0buf[TZBUFSIZE];
+  timezone_t tz = tzdefault;
   bool ok = true;
   char dbg_ord[DBGBUFSIZE];
   char dbg_tm[DBGBUFSIZE];
   char const *input_sentinel = p + strlen (p);
+  char *tz1alloc = NULL;
+  char tz1buf[TZBUFSIZE];
 
   if (! now)
     {
@@ -1760,10 +1751,6 @@ parse_datetime2 (struct timespec *result, char const *p,
   Start = now->tv_sec;
   Start_ns = now->tv_nsec;
 
-  tmp = localtime (&now->tv_sec);
-  if (! tmp)
-    return false;
-
   while (c = *p, c_isspace (c))
     p++;
 
@@ -1782,22 +1769,25 @@ parse_datetime2 (struct timespec *result, char const *p,
           }
         else if (*s == '"')
           {
+            timezone_t tz1;
+            char *tz1string = tz1buf;
             char *z;
-            char *tz1;
-            char tz1buf[TZBUFSIZE];
-            bool large_tz = TZBUFSIZE < tzsize;
-            bool setenv_ok;
-            tz0 = get_tz (tz0buf);
-            z = tz1 = large_tz ? xmalloc (tzsize) : tz1buf;
+            if (TZBUFSIZE < tzsize)
+              {
+                tz1alloc = malloc (tzsize);
+                if (!tz1alloc)
+                  goto fail;
+                tz1string = tz1alloc;
+              }
+            z = tz1string;
             for (s = tzbase; *s != '"'; s++)
               *z++ = *(s += *s == '\\');
             *z = '\0';
-            setenv_ok = setenv ("TZ", tz1, 1) == 0;
-            if (large_tz)
-              free (tz1);
-            if (!setenv_ok)
+            tz1 = tzalloc (tz1string);
+            if (!tz1)
               goto fail;
-            tz_was_altered = true;
+            tz = tz1;
+            tzstring = tz1string;
 
             p = s + 1;
             while (c = *p, c_isspace (c))
@@ -1807,6 +1797,9 @@ parse_datetime2 (struct timespec *result, char const *p,
           }
     }
 
+  if (! localtime_rz (tz, &now->tv_sec, &tmp))
+    return false;
+
   /* As documented, be careful to treat the empty string just like
      a date string of "0".  Without this, an empty string would be
      declared invalid when parsed during a DST transition.  */
@@ -1814,16 +1807,16 @@ parse_datetime2 (struct timespec *result, char const *p,
     p = "0";
 
   pc.input = p;
-  pc.year.value = tmp->tm_year;
+  pc.year.value = tmp.tm_year;
   pc.year.value += TM_YEAR_BASE;
   pc.year.digits = 0;
-  pc.month = tmp->tm_mon + 1;
-  pc.day = tmp->tm_mday;
-  pc.hour = tmp->tm_hour;
-  pc.minutes = tmp->tm_min;
-  pc.seconds.tv_sec = tmp->tm_sec;
+  pc.month = tmp.tm_mon + 1;
+  pc.day = tmp.tm_mday;
+  pc.hour = tmp.tm_hour;
+  pc.minutes = tmp.tm_min;
+  pc.seconds.tv_sec = tmp.tm_sec;
   pc.seconds.tv_nsec = Start_ns;
-  tm.tm_isdst = tmp->tm_isdst;
+  tm.tm_isdst = tmp.tm_isdst;
 
   pc.meridian = MER24;
   pc.rel = RELATIVE_TIME_0;
@@ -1848,9 +1841,9 @@ parse_datetime2 (struct timespec *result, char const *p,
   pc.debug_default_input_timezone = 0;
 
 #if HAVE_STRUCT_TM_TM_ZONE
-  pc.local_time_zone_table[0].name = tmp->tm_zone;
+  pc.local_time_zone_table[0].name = tmp.tm_zone;
   pc.local_time_zone_table[0].type = tLOCAL_ZONE;
-  pc.local_time_zone_table[0].value = tmp->tm_isdst;
+  pc.local_time_zone_table[0].value = tmp.tm_isdst;
   pc.local_time_zone_table[1].name = NULL;
 
   /* Probe the names used in the next three calendar quarters, looking
@@ -1860,14 +1853,14 @@ parse_datetime2 (struct timespec *result, char const *p,
     for (quarter = 1; quarter <= 3; quarter++)
       {
         time_t probe = Start + quarter * (90 * 24 * 60 * 60);
-        struct tm const *probe_tm = localtime (&probe);
-        if (probe_tm && probe_tm->tm_zone
-            && probe_tm->tm_isdst != pc.local_time_zone_table[0].value)
+        struct tm probe_tm;
+        if (localtime_rz (tz, &probe, &probe_tm) && probe_tm.tm_zone
+            && probe_tm.tm_isdst != pc.local_time_zone_table[0].value)
           {
               {
-                pc.local_time_zone_table[1].name = probe_tm->tm_zone;
+                pc.local_time_zone_table[1].name = probe_tm.tm_zone;
                 pc.local_time_zone_table[1].type = tLOCAL_ZONE;
-                pc.local_time_zone_table[1].value = probe_tm->tm_isdst;
+                pc.local_time_zone_table[1].value = probe_tm.tm_isdst;
                 pc.local_time_zone_table[2].name = NULL;
               }
             break;
@@ -1905,7 +1898,7 @@ parse_datetime2 (struct timespec *result, char const *p,
       pc.local_time_zone_table[1].name = NULL;
     }
 
-  pc.debug_default_input_timezone = get_effective_timezone ();
+  pc.debug_default_input_timezone = get_effective_timezone (tz);
 
   if (yyparse (&pc) != 0)
     {
@@ -1925,27 +1918,26 @@ parse_datetime2 (struct timespec *result, char const *p,
   /* determine effective timezone source */
   if (pc.parse_datetime_debug)
     {
-      long int tz = pc.debug_default_input_timezone;
-      const char* tz_env;
+      long int time_zone = pc.debug_default_input_timezone;
 
       if (pc.timespec_seen)
         {
-          tz = 0 ;
+          time_zone = 0;
           strncpy (dbg_tm, _("'@timespec' - always UTC0"), sizeof (dbg_tm)-1);
         }
       else if (pc.zones_seen)
         {
-          tz = pc.time_zone;
+          time_zone = pc.time_zone;
           strncpy (dbg_tm, _("parsed date/time string"), sizeof (dbg_tm)-1);
         }
-      else if ((tz_env = getenv("TZ")))
+      else if (tzstring)
         {
-          if (tz_was_altered)
+          if (tz != tzdefault)
             {
               snprintf (dbg_tm, sizeof(dbg_tm), _("TZ=\"%s\" in date string"),
-                        tz_env);
+                        tzstring);
             }
-          else if (STREQ(tz_env,"UTC0"))
+          else if (STREQ (tzstring, "UTC0"))
             {
               /* Special case: using 'date -u' simply set TZ=UTC0 */
               strncpy (dbg_tm, _("TZ=UTC0 environment value or -u"),
@@ -1954,7 +1946,7 @@ parse_datetime2 (struct timespec *result, char const *p,
           else
             {
               snprintf (dbg_tm, sizeof(dbg_tm),
-                        _("TZ=\"%s\" environment value"), tz_env);
+                        _("TZ=\"%s\" environment value"), tzstring);
             }
         }
       else
@@ -1970,14 +1962,15 @@ parse_datetime2 (struct timespec *result, char const *p,
          default timezone.*/
       if (pc.local_zones_seen && !pc.zones_seen && pc.local_isdst==1)
         {
-          tz += 60;
+          time_zone += 60;
           strncat (dbg_tm, ", dst",
                    sizeof (dbg_tm) - strlen (dbg_tm) - 1);
         }
 
       if (pc.parse_datetime_debug)
         dbg_printf (_("input timezone: %+03d:%02d (set from %s)\n"),
-                    (int)(tz/60), abs ((int)tz)%60, dbg_tm);
+                    (int) (time_zone / 60), abs ((int) (time_zone % 60)),
+                    dbg_tm);
 
     }
 
@@ -2045,9 +2038,9 @@ parse_datetime2 (struct timespec *result, char const *p,
 
       tm0 = tm;
 
-      Start = mktime (&tm);
+      Start = mktime_z (tz, &tm);
 
-      if (! mktime_ok (&tm0, &tm, Start))
+      if (! mktime_ok (tz, &tm0, &tm, Start))
         {
           if (! pc.zones_seen)
             {
@@ -2071,27 +2064,27 @@ parse_datetime2 (struct timespec *result, char const *p,
               long int abs_time_zone = time_zone < 0 ? - time_zone : time_zone;
               long int abs_time_zone_hour = abs_time_zone / 60;
               int abs_time_zone_min = abs_time_zone % 60;
-              char tz1buf[sizeof "XXX+0:00" + TYPE_WIDTH (pc.time_zone) / 3];
-              if (!tz_was_altered)
-                tz0 = get_tz (tz0buf);
-              sprintf (tz1buf, "XXX%s%ld:%02d", &"-"[time_zone < 0],
+              char tz2buf[sizeof "XXX+0:00" + TYPE_WIDTH (pc.time_zone) / 3];
+              timezone_t tz2;
+              sprintf (tz2buf, "XXX%s%ld:%02d", &"-"[time_zone < 0],
                        abs_time_zone_hour, abs_time_zone_min);
-              if (setenv ("TZ", tz1buf, 1) != 0)
+              tz2 = tzalloc (tz2buf);
+              if (!tz2)
                 {
-                  /* TODO: was warn () + print errno? */
                   if (pc.parse_datetime_debug)
-                    dbg_printf (_("error: setenv('TZ','%s') failed\n"), 
tz1buf);
+                    dbg_printf (_("error: tzalloc (\"%s\") failed\n"), tz2buf);
 
                   goto fail;
                 }
-              tz_was_altered = true;
               tm = tm0;
-              Start = mktime (&tm);
-              if (! mktime_ok (&tm0, &tm, Start))
+              Start = mktime_z (tz2, &tm);
+              ok = mktime_ok (tz2, &tm0, &tm, Start);
+              tzfree (tz2);
+              if (! ok)
                 {
                   debug_mktime_not_ok (&tm0, &tm, &pc, pc.zones_seen);
 
-                  goto fail;
+                  goto done;
                 }
             }
         }
@@ -2103,7 +2096,7 @@ parse_datetime2 (struct timespec *result, char const *p,
                                 - (0 < pc.day_ordinal
                                    && tm.tm_wday != pc.day_number)));
           tm.tm_isdst = -1;
-          Start = mktime (&tm);
+          Start = mktime_z (tz, &tm);
           if (Start == (time_t) -1)
             {
               if (pc.parse_datetime_debug)
@@ -2174,7 +2167,7 @@ parse_datetime2 (struct timespec *result, char const *p,
           tm.tm_min = tm0.tm_min;
           tm.tm_sec = tm0.tm_sec;
           tm.tm_isdst = tm0.tm_isdst;
-          Start = mktime (&tm);
+          Start = mktime_z (tz, &tm);
           if (Start == (time_t) -1)
             {
               if (pc.parse_datetime_debug)
@@ -2250,8 +2243,8 @@ parse_datetime2 (struct timespec *result, char const *p,
           delta -= tm.tm_gmtoff;
 #else
           time_t t = Start;
-          struct tm const *gmt = gmtime (&t);
-          if (! gmt)
+          struct tm gmt;
+          if (! gmtime_r (&t, &gmt))
             {
               /* TODO: use 'warn(3)' + print errno ? */
               if (pc.parse_datetime_debug)
@@ -2259,7 +2252,7 @@ parse_datetime2 (struct timespec *result, char const *p,
 
               goto fail;
             }
-          delta -= tm_diff (&tm, gmt);
+          delta -= tm_diff (&tm, &gmt);
 #endif
           t1 = Start - delta;
           if ((Start < t1) != (delta < 0))
@@ -2317,6 +2310,7 @@ parse_datetime2 (struct timespec *result, char const *p,
           if (pc.parse_datetime_debug
               && (pc.rel.hour | pc.rel.minutes | pc.rel.seconds | pc.rel.ns))
             {
+              struct tm lmt;
               dbg_printf (_("after time adjustment (%+ld hours, " \
                             "%+ld minutes, %+ld seconds, %+ld ns),\n"),
                           pc.rel.hour,pc.rel.minutes,pc.rel.seconds,pc.rel.ns);
@@ -2334,8 +2328,8 @@ parse_datetime2 (struct timespec *result, char const *p,
 
                  'tm.tm_isdst' contains the date after date adjustment.
               */
-              struct tm const *lmt = localtime (&t5);
-              if ((tm.tm_isdst!=-1) && (tm.tm_isdst != lmt->tm_isdst))
+              if (tm.tm_isdst != -1 && localtime_rz (tz, &t5, &lmt)
+                  && tm.tm_isdst != lmt.tm_isdst)
                 dbg_printf (_("warning: daylight saving time changed after " \
                               "time adjustment\n"));
             }
@@ -2350,29 +2344,22 @@ parse_datetime2 (struct timespec *result, char const *p,
  fail:
   ok = false;
  done:
-  if (tz_was_altered)
-    ok &= (tz0 ? setenv ("TZ", tz0, 1) : unsetenv ("TZ")) == 0;
-  if (tz0 != tz0buf)
-    free (tz0);
-
   if (ok && pc.parse_datetime_debug)
     {
-      /* print local timezone AFTER restoring TZ (if tz_was_altered)*/
-      const long int otz = get_effective_timezone ();
-      const char* tz_src;
-      const char* tz_env;
+      const long int otz = get_effective_timezone (tz);
+      const char *tz_src;
 
-      if ((tz_env = getenv("TZ")))
+      if (tzstring)
         {
           /* Special case: using 'date -u' simply set TZ=UTC0 */
-          if (STREQ(tz_env,"UTC0"))
+          if (STREQ (tzstring, "UTC0"))
             {
               tz_src = _("TZ=UTC0 environment value or -u");
             }
           else
             {
               snprintf (dbg_tm, sizeof(dbg_tm),
-                        _("TZ=\"%s\" environment value"), tz_env);
+                        _("TZ=\"%s\" environment value"), tzstring);
               tz_src = dbg_tm;
             }
         }
@@ -2390,16 +2377,21 @@ parse_datetime2 (struct timespec *result, char const *p,
           dbg_printf (_("final: %ld.%09ld (epoch-seconds)\n"),
                       result->tv_sec,result->tv_nsec);
 
-          struct tm const *gmt = gmtime (&result->tv_sec);
-          dbg_printf (_("final: %s (UTC0)\n"),
-                      debug_strfdatetime (gmt, NULL, dbg_tm, sizeof (dbg_tm)));
-          struct tm const *lmt = localtime (&result->tv_sec);
-          dbg_printf (_("final: %s (output timezone TZ=%+03d:%02d)\n"),
-                      debug_strfdatetime (lmt, NULL, dbg_tm, sizeof (dbg_tm)),
-                      (int)(otz/60), abs ((int)otz)%60);
+          struct tm gmt, lmt;
+          if (gmtime_r (&result->tv_sec, &gmt))
+            dbg_printf (_("final: %s (UTC0)\n"),
+                        debug_strfdatetime (&gmt, NULL,
+                                            dbg_tm, sizeof dbg_tm));
+          if (localtime_rz (tz, &result->tv_sec, &lmt))
+            dbg_printf (_("final: %s (output timezone TZ=%+03d:%02d)\n"),
+                        debug_strfdatetime (&lmt, NULL, dbg_tm, sizeof dbg_tm),
+                        (int) (otz / 60), abs ((int) (otz % 60)));
         }
     }
 
+  if (tz != tzdefault)
+    tzfree (tz);
+  free (tz1alloc);
   return ok;
 }
 
diff --git a/modules/parse-datetime b/modules/parse-datetime
index 20d4236..5d10137 100644
--- a/modules/parse-datetime
+++ b/modules/parse-datetime
@@ -20,9 +20,10 @@ setenv
 strftime
 unsetenv
 time
+time_r
+time_rz
 timegm
 verify
-xalloc
 
 configure.ac:
 gl_PARSE_DATETIME
-- 
2.9.3




reply via email to

[Prev in Thread] Current Thread [Next in Thread]