Blob Blame History Raw
commit 42261ad731991df345880b0b509d83b0b9a9b9d8
Author: Florian Weimer <fweimer@redhat.com>
Date:   Fri Apr 24 17:34:47 2015 +0200

    Make time zone file parser more robust [BZ #17715]

commit 6807b1db8233ed84671f061b5d825622233df303
Author: Kevin Easton <kevin@guarana.org>
Date:   Tue Feb 24 23:57:07 2015 -0500

    Reduce lock contention in __tz_convert() [BZ #16145] (partial fix)

commit 9d46370ca338054cb6ea7ebeddcf06c7ac7ad1a9
Author: Joseph Myers <joseph@codesourcery.com>
Date:   Fri Oct 16 20:21:49 2015 +0000

    Convert 703 function definitions to prototype style.
    (A subset of these changes (tzset.c) were applied as part of this patch.)

commit 0748546f660d27a2ad29fa6174d456e2f6490758
Author: Paul Eggert <eggert@cs.ucla.edu>
Date:   Wed Sep 18 13:15:12 2013 -0700

    Support TZ transition times < 00:00:00.

    This is needed for version-3 tz-format files; it supports time
    stamps past 2037 for America/Godthab (the only entry in the tz
    database for which this change is relevant).
    * manual/time.texi (TZ Variable): Document transition times
    from -167:59:59 through -00:00:01.
    * time/tzset.c (tz_rule): Time of day is now signed.
    (__tzset_parse_tz): Parse negative time of day.
    (A subset of these changes were applied as part of this patch.)

commit 3cc652e951c71785032019fec82e3b8543d85305
Author: Mike Frysinger <vapier@gentoo.org>
Date:   Fri Sep 18 13:49:08 2015 -0400
 
   timezone: fix parallel check failures
   
   The XT testdata install rules expect the testdata dir to already exist in
   the build tree, but it doesn't actually create it.  Instead, it relies on
   the build-testdata define happening to be executed before it (which runs
   zic which creates the dir).  When we run in parallel though, it's easy to
   hit a failure:
   $ cd timezone
   $ rm -rf $objdir/timezone/testdata
   $ make check -j
   ...
   cp testdata/XT1 .../timezone/testdata/XT1
   cp: cannot create regular file '.../timezone/testdata/XT1': No such file or directory
   Makefile:116: recipe for target '.../timezone/testdata/XT1' failed
   make: *** [.../timezone/testdata/XT1] Error 1
   make: *** Waiting for unfinished jobs....

diff --git a/time/tzfile.c b/time/tzfile.c
--- a/time/tzfile.c
+++ b/time/tzfile.c
@@ -213,6 +213,9 @@
   num_isstd = (size_t) decode (tzhead.tzh_ttisstdcnt);
   num_isgmt = (size_t) decode (tzhead.tzh_ttisgmtcnt);
 
+  if (__glibc_unlikely (num_isstd > num_types || num_isgmt > num_types))
+    goto lose;
+
   /* For platforms with 64-bit time_t we use the new format if available.  */
   if (sizeof (time_t) == 8 && trans_width == 4
       && tzhead.tzh_version[0] != '\0')
@@ -445,12 +448,20 @@
 	goto lose;
 
       tzspec_len = st.st_size - off - 1;
-      char *tzstr = alloca (tzspec_len);
+      if (tzspec_len == 0)
+        goto lose;
+      char *tzstr = malloc (tzspec_len);
+      if (tzstr == NULL)
+        goto lose;
       if (getc_unlocked (f) != '\n'
 	  || (fread_unlocked (tzstr, 1, tzspec_len - 1, f) != tzspec_len - 1))
-	goto lose;
+        {
+          free (tzstr);
+          goto lose;
+        }
       tzstr[tzspec_len - 1] = '\0';
       tzspec = __tzstring (tzstr);
+      free (tzstr);
     }
 
   /* Don't use an empty TZ string.  */

diff --git a/time/tzset.c b/time/tzset.c
--- a/time/tzset.c
+++ b/time/tzset.c
@@ -1,4 +1,4 @@
-/* Copyright (C) 1991-2012 Free Software Foundation, Inc.
+/* Copyright (C) 1991-2016 Free Software Foundation, Inc.
    This file is part of the GNU C Library.
 
    The GNU C Library is free software; you can redistribute it and/or
@@ -18,6 +18,7 @@
 #include <ctype.h>
 #include <errno.h>
 #include <bits/libc-lock.h>
+#include <stdbool.h>
 #include <stddef.h>
 #include <stdio.h>
 #include <stdlib.h>
@@ -54,7 +55,7 @@
     /* When to change.  */
     enum { J0, J1, M } type;	/* Interpretation of:  */
     unsigned short int m, n, d;	/* Month, week, day.  */
-    unsigned int secs;		/* Time of day.  */
+    int secs;			/* Time of day.  */
 
     long int offset;		/* Seconds east of GMT (west if < 0).  */
 
@@ -82,15 +83,14 @@
 
 static struct tzstring_l *tzstring_list;
 
-/* Allocate a permanent home for S.  It will never be moved or deallocated,
-   but may share space with other strings.
-   Don't modify the returned string. */
-char *
-__tzstring (const char *s)
+/* Allocate a permanent home for the first LEN characters of S.  It
+   will never be moved or deallocated, but may share space with other
+   strings.  Don't modify the returned string. */
+static char *
+__tzstring_len (const char *s, size_t len)
 {
   char *p;
   struct tzstring_l *t, *u, *new;
-  size_t len = strlen (s);
 
   /* Walk the list and look for a match.  If this string is the same
      as the end of an already-allocated string, it can share space. */
@@ -98,7 +98,7 @@
     if (len <= t->len)
       {
 	p = &t->data[t->len - len];
-	if (strcmp (s, p) == 0)
+	if (memcmp (s, p, len) == 0)
 	  return p;
       }
 
@@ -109,7 +109,8 @@
 
   new->next = NULL;
   new->len = len;
-  strcpy (new->data, s);
+  memcpy (new->data, s, len);
+  new->data[len] = '\0';
 
   if (u)
     u->next = new;
@@ -118,6 +119,15 @@
 
   return new->data;
 }
+
+/* Allocate a permanent home for S.  It will never be moved or
+   deallocated, but may share space with other strings.  Don't modify
+   the returned string. */
+char *
+__tzstring (const char *s)
+{
+  return __tzstring_len (s, strlen (s));
+}
 
 /* Maximum length of a timezone name.  tzset_internal keeps this up to date
    (never decreasing it) when ! __use_tzfile.
@@ -125,7 +135,7 @@
 size_t __tzname_cur_max;
 
 long int
-__tzname_max ()
+__tzname_max (void)
 {
   __libc_lock_lock (tzset_lock);
 
@@ -164,243 +174,227 @@
   return min (ss, 59) + min (mm, 59) * 60 + min (hh, 24) * 60 * 60;
 }
 
-
-/* Parse the POSIX TZ-style string.  */
-void
-__tzset_parse_tz (tz)
-     const char *tz;
-{
-  unsigned short int hh, mm, ss;
-
-  /* Clear out old state and reset to unnamed UTC.  */
-  memset (tz_rules, '\0', sizeof tz_rules);
-  tz_rules[0].name = tz_rules[1].name = "";
-
-  /* Get the standard timezone name.  */
-  char *tzbuf = strdupa (tz);
-
-  int consumed;
-  if (sscanf (tz, "%[A-Za-z]%n", tzbuf, &consumed) != 1)
-    {
-      /* Check for the quoted version.  */
-      char *wp = tzbuf;
-      if (__builtin_expect (*tz++ != '<', 0))
-	goto out;
-
-      while (isalnum (*tz) || *tz == '+' || *tz == '-')
-	*wp++ = *tz++;
-      if (__builtin_expect (*tz++ != '>' || wp - tzbuf < 3, 0))
-	goto out;
-      *wp = '\0';
+/* Parses the time zone name at *TZP, and writes a pointer to an
+   interned string to tz_rules[WHICHRULE].name.  On success, advances
+   *TZP, and returns true.  Returns false otherwise.  */
+static bool
+parse_tzname (const char **tzp, int whichrule)
+{
+  const char *start = *tzp;
+  const char *p = start;
+  while (('a' <= *p && *p <= 'z')
+	 || ('A' <= *p && *p <= 'Z'))
+      ++p;
+  size_t len = p - start;
+  if (len < 3)
+    {
+      p = *tzp;
+      if (__glibc_unlikely (*p++ != '<'))
+	return false;
+      start = p;
+      while (('a' <= *p && *p <= 'z')
+	     || ('A' <= *p && *p <= 'Z')
+	     || ('0' <= *p && *p <= '9')
+	     || *p == '+' || *p == '-')
+	++p;
+      len = p - start;
+      if (*p++ != '>' || len < 3)
+	return false;
     }
-  else if (__builtin_expect (consumed < 3, 0))
-    goto out;
-  else
-    tz += consumed;
 
-  tz_rules[0].name = __tzstring (tzbuf);
+  tz_rules[whichrule].name = __tzstring_len (start, len);
+
+  *tzp = p;
+  return true;
+}
 
-  /* Figure out the standard offset from UTC.  */
-  if (*tz == '\0' || (*tz != '+' && *tz != '-' && !isdigit (*tz)))
-    goto out;
+/* Parses the time zone offset at *TZP, and writes it to
+   tz_rules[WHICHRULE].offset.  Returns true if the parse was
+   successful.  */
+static bool
+parse_offset (const char **tzp, int whichrule)
+{
+  const char *tz = *tzp;
+  if (whichrule == 0
+      && (*tz == '\0' || (*tz != '+' && *tz != '-' && !isdigit (*tz))))
+    return false;
 
+  long sign;
   if (*tz == '-' || *tz == '+')
-    tz_rules[0].offset = *tz++ == '-' ? 1L : -1L;
+    sign = *tz++ == '-' ? 1L : -1L;
   else
-    tz_rules[0].offset = -1L;
-  switch (sscanf (tz, "%hu%n:%hu%n:%hu%n",
-		  &hh, &consumed, &mm, &consumed, &ss, &consumed))
-    {
-    default:
-      tz_rules[0].offset = 0;
-      goto out;
-    case 1:
-      mm = 0;
-    case 2:
-      ss = 0;
-    case 3:
-      break;
-    }
-  tz_rules[0].offset *= compute_offset (ss, mm, hh);
-  tz += consumed;
-
-  /* Get the DST timezone name (if any).  */
-  if (*tz != '\0')
-    {
-      if (sscanf (tz, "%[A-Za-z]%n", tzbuf, &consumed) != 1)
-	{
-	  /* Check for the quoted version.  */
-	  char *wp = tzbuf;
-	  const char *rp = tz;
-	  if (__builtin_expect (*rp++ != '<', 0))
-	    /* Punt on name, set up the offsets.  */
-	    goto done_names;
-
-	  while (isalnum (*rp) || *rp == '+' || *rp == '-')
-	    *wp++ = *rp++;
-	  if (__builtin_expect (*rp++ != '>' || wp - tzbuf < 3, 0))
-	    /* Punt on name, set up the offsets.  */
-	    goto done_names;
-	  *wp = '\0';
-	  tz = rp;
-	}
-      else if (__builtin_expect (consumed < 3, 0))
-	/* Punt on name, set up the offsets.  */
-	goto done_names;
-      else
-	tz += consumed;
+    sign = -1L;
+  *tzp = tz;
 
-      tz_rules[1].name = __tzstring (tzbuf);
-
-      /* Figure out the DST offset from GMT.  */
-      if (*tz == '-' || *tz == '+')
-	tz_rules[1].offset = *tz++ == '-' ? 1L : -1L;
+  unsigned short int hh;
+  unsigned short mm = 0;
+  unsigned short ss = 0;
+  int consumed = 0;
+  if (sscanf (tz, "%hu%n:%hu%n:%hu%n",
+	      &hh, &consumed, &mm, &consumed, &ss, &consumed) > 0)
+    tz_rules[whichrule].offset = sign * compute_offset (ss, mm, hh);
+  else
+    /* Nothing could be parsed. */
+    if (whichrule == 0)
+      {
+	/* Standard time defaults to offset zero.  */
+	tz_rules[0].offset = 0;
+	return false;
+      }
       else
-	tz_rules[1].offset = -1L;
+	/* DST defaults to one hour later than standard time.  */
+	tz_rules[1].offset = tz_rules[0].offset + (60 * 60);
+  *tzp = tz + consumed;
+  return true;
+}
 
-      switch (sscanf (tz, "%hu%n:%hu%n:%hu%n",
-		      &hh, &consumed, &mm, &consumed, &ss, &consumed))
+/* Parses the standard <-> DST rules at *TZP.  Updates
+   tz_rule[WHICHRULE].  On success, advances *TZP and returns true.
+   Otherwise, returns false.  */
+static bool
+parse_rule (const char **tzp, int whichrule)
+{
+  const char *tz = *tzp;
+  tz_rule *tzr = &tz_rules[whichrule];
+
+  /* Ignore comma to support string following the incorrect
+     specification in early POSIX.1 printings.  */
+  tz += *tz == ',';
+
+  /* Get the date of the change.  */
+  if (*tz == 'J' || isdigit (*tz))
+    {
+      char *end;
+      tzr->type = *tz == 'J' ? J1 : J0;
+      if (tzr->type == J1 && !isdigit (*++tz))
+	return false;
+      unsigned long int d = strtoul (tz, &end, 10);
+      if (end == tz || d > 365)
+	return false;
+      if (tzr->type == J1 && d == 0)
+	return false;
+      tzr->d = d;
+      tz = end;
+    }
+  else if (*tz == 'M')
+    {
+      tzr->type = M;
+      int consumed;
+      if (sscanf (tz, "M%hu.%hu.%hu%n",
+		  &tzr->m, &tzr->n, &tzr->d, &consumed) != 3
+	  || tzr->m < 1 || tzr->m > 12
+	  || tzr->n < 1 || tzr->n > 5 || tzr->d > 6)
+	return false;
+      tz += consumed;
+    }
+  else if (*tz == '\0')
+    {
+      /* Daylight time rules in the U.S. are defined in the U.S. Code,
+	 Title 15, Chapter 6, Subchapter IX - Standard Time.  These
+	 dates were established by Congress in the Energy Policy Act
+	 of 2005 [Pub. L. no. 109-58, 119 Stat 594 (2005)].
+	 Below is the equivalent of "M3.2.0,M11.1.0" [/2 not needed
+	 since 2:00AM is the default].  */
+      tzr->type = M;
+      if (tzr == &tz_rules[0])
 	{
-	default:
-	  /* Default to one hour later than standard time.  */
-	  tz_rules[1].offset = tz_rules[0].offset + (60 * 60);
-	  break;
-
-	case 1:
-	  mm = 0;
-	case 2:
-	  ss = 0;
-	case 3:
-	  tz_rules[1].offset *= compute_offset (ss, mm, hh);
-	  tz += consumed;
-	  break;
+	  tzr->m = 3;
+	  tzr->n = 2;
+	  tzr->d = 0;
 	}
-      if (*tz == '\0' || (tz[0] == ',' && tz[1] == '\0'))
+      else
 	{
-	  /* There is no rule.  See if there is a default rule file.  */
-	  __tzfile_default (tz_rules[0].name, tz_rules[1].name,
-			    tz_rules[0].offset, tz_rules[1].offset);
-	  if (__use_tzfile)
-	    {
-	      free (old_tz);
-	      old_tz = NULL;
-	      return;
-	    }
+	  tzr->m = 11;
+	  tzr->n = 1;
+	  tzr->d = 0;
 	}
     }
   else
-    {
-      /* There is no DST.  */
-      tz_rules[1].name = tz_rules[0].name;
-      tz_rules[1].offset = tz_rules[0].offset;
-      goto out;
+    return false;
+
+  if (*tz != '\0' && *tz != '/' && *tz != ',')
+    return false;
+  else if (*tz == '/')
+    {
+      /* Get the time of day of the change.  */
+      int negative;
+      ++tz;
+      if (*tz == '\0')
+	return false;
+      negative = *tz == '-';
+      tz += negative;
+      /* Default to 2:00 AM.  */
+      unsigned short hh = 2;
+      unsigned short mm = 0;
+      unsigned short ss = 0;
+      int consumed = 0;
+      sscanf (tz, "%hu%n:%hu%n:%hu%n",
+	      &hh, &consumed, &mm, &consumed, &ss, &consumed);;
+      tz += consumed;
+      tzr->secs = (negative ? -1 : 1) * ((hh * 60 * 60) + (mm * 60) + ss);
     }
+  else
+    /* Default to 2:00 AM.  */
+    tzr->secs = 2 * 60 * 60;
 
- done_names:
-  /* Figure out the standard <-> DST rules.  */
-  for (unsigned int whichrule = 0; whichrule < 2; ++whichrule)
-    {
-      register tz_rule *tzr = &tz_rules[whichrule];
+  tzr->computed_for = -1;
+  *tzp = tz;
+  return true;
+}
 
-      /* Ignore comma to support string following the incorrect
-	 specification in early POSIX.1 printings.  */
-      tz += *tz == ',';
+/* Parse the POSIX TZ-style string.  */
+void
+__tzset_parse_tz (const char *tz)
+{
+  /* Clear out old state and reset to unnamed UTC.  */
+  memset (tz_rules, '\0', sizeof tz_rules);
+  tz_rules[0].name = tz_rules[1].name = "";
 
-      /* Get the date of the change.  */
-      if (*tz == 'J' || isdigit (*tz))
-	{
-	  char *end;
-	  tzr->type = *tz == 'J' ? J1 : J0;
-	  if (tzr->type == J1 && !isdigit (*++tz))
-	    goto out;
-	  unsigned long int d = strtoul (tz, &end, 10);
-	  if (end == tz || d > 365)
-	    goto out;
-	  if (tzr->type == J1 && d == 0)
-	    goto out;
-	  tzr->d = d;
-	  tz = end;
-	}
-      else if (*tz == 'M')
-	{
-	  tzr->type = M;
-	  if (sscanf (tz, "M%hu.%hu.%hu%n",
-		      &tzr->m, &tzr->n, &tzr->d, &consumed) != 3
-	      || tzr->m < 1 || tzr->m > 12
-	      || tzr->n < 1 || tzr->n > 5 || tzr->d > 6)
-	    goto out;
-	  tz += consumed;
-	}
-      else if (*tz == '\0')
+  /* Get the standard timezone name.  */
+  if (parse_tzname (&tz, 0) && parse_offset (&tz, 0))
+    {
+      /* Get the DST timezone name (if any).  */
+      if (*tz != '\0')
 	{
-         /* Daylight time rules in the U.S. are defined in the
-            U.S. Code, Title 15, Chapter 6, Subchapter IX - Standard
-            Time.  These dates were established by Congress in the
-            Energy Policy Act of 2005 [Pub. L. no. 109-58, 119 Stat 594
-            (2005)].
-	    Below is the equivalent of "M3.2.0,M11.1.0" [/2 not needed
-	    since 2:00AM is the default].  */
-	  tzr->type = M;
-	  if (tzr == &tz_rules[0])
+	  if (parse_tzname (&tz, 1))
 	    {
-	      tzr->m = 3;
-	      tzr->n = 2;
-	      tzr->d = 0;
-	    }
-	  else
-	    {
-	      tzr->m = 11;
-	      tzr->n = 1;
-	      tzr->d = 0;
+	      parse_offset (&tz, 1);
+	      if (*tz == '\0' || (tz[0] == ',' && tz[1] == '\0'))
+		{
+		  /* There is no rule.  See if there is a default rule
+		     file.  */
+		  __tzfile_default (tz_rules[0].name, tz_rules[1].name,
+				    tz_rules[0].offset, tz_rules[1].offset);
+		  if (__use_tzfile)
+		    {
+		      free (old_tz);
+		      old_tz = NULL;
+		      return;
+		    }
+		}
 	    }
+	  /* Figure out the standard <-> DST rules.  */
+	  if (parse_rule (&tz, 0))
+	    parse_rule (&tz, 1);
 	}
       else
-	goto out;
-
-      if (*tz != '\0' && *tz != '/' && *tz != ',')
-	goto out;
-      else if (*tz == '/')
 	{
-	  /* Get the time of day of the change.  */
-	  ++tz;
-	  if (*tz == '\0')
-	    goto out;
-	  consumed = 0;
-	  switch (sscanf (tz, "%hu%n:%hu%n:%hu%n",
-			  &hh, &consumed, &mm, &consumed, &ss, &consumed))
-	    {
-	    default:
-	      hh = 2;		/* Default to 2:00 AM.  */
-	    case 1:
-	      mm = 0;
-	    case 2:
-	      ss = 0;
-	    case 3:
-	      break;
-	    }
-	  tz += consumed;
-	  tzr->secs = (hh * 60 * 60) + (mm * 60) + ss;
+	  /* There is no DST.  */
+	  tz_rules[1].name = tz_rules[0].name;
+	  tz_rules[1].offset = tz_rules[0].offset;
 	}
-      else
-	/* Default to 2:00 AM.  */
-	tzr->secs = 2 * 60 * 60;
-
-      tzr->computed_for = -1;
     }
 
- out:
   update_vars ();
 }
 
 /* Interpret the TZ envariable.  */
 static void
 internal_function
-tzset_internal (always, explicit)
-     int always;
-     int explicit;
+tzset_internal (int always, int explicit)
 {
   static int is_initialized;
-  register const char *tz;
+  const char *tz;
 
   if (is_initialized && !always)
     return;
@@ -467,11 +461,9 @@
    put it in RULE->change, saving YEAR in RULE->computed_for.  */
 static void
 internal_function
-compute_change (rule, year)
-     tz_rule *rule;
-     int year;
+compute_change (tz_rule *rule, int year)
 {
-  register time_t t;
+  time_t t;
 
   if (year != -1 && rule->computed_for == year)
     /* Operations on times in 2 BC will be slower.  Oh well.  */
@@ -558,10 +550,7 @@
    `__timezone', and `__daylight' accordingly.  */
 void
 internal_function
-__tz_compute (timer, tm, use_localtime)
-     time_t timer;
-     struct tm *tm;
-     int use_localtime;
+__tz_compute (time_t timer, struct tm *tm, int use_localtime)
 {
   compute_change (&tz_rules[0], 1900 + tm->tm_year);
   compute_change (&tz_rules[1], 1900 + tm->tm_year);
@@ -641,6 +630,8 @@
       leap_extra_secs = 0;
     }
 
+  __libc_lock_unlock (tzset_lock);
+
   if (tp)
     {
       if (! use_localtime)
@@ -656,8 +647,6 @@
 	tp = NULL;
     }
 
-  __libc_lock_unlock (tzset_lock);
-
   return tp;
 }
 
diff --git a/timezone/Makefile b/timezone/Makefile
index 17424b8..5f18545 100644
--- a/timezone/Makefile
+++ b/timezone/Makefile
@@ -23,7 +23,7 @@
 extra-objs := scheck.o ialloc.o
 
 others	:= zdump zic
-tests	:= test-tz tst-timezone
+tests	:= test-tz tst-timezone tst-tzset
 
 # pacificnew doesn't compile; if it is to be used, it should be included in
 # northamerica.
@@ -87,9 +87,11 @@
 				       Australia/Melbourne \
 				       America/Sao_Paulo Asia/Tokyo \
 				       Europe/London)
+$(objpfx)tst-tzset.out: $(addprefix $(testdata)/XT, 1 2 3 4)
 
 test-tz-ENV = TZDIR=$(testdata)
 tst-timezone-ENV = TZDIR=$(testdata)
+tst-tzset-ENV = TZDIR=$(testdata)
 
 # Note this must come second in the deps list for $(built-program-cmd) to work.
 zic-deps = $(objpfx)zic $(leapseconds) yearistype
@@ -111,6 +113,8 @@
 $(testdata)/Asia/Tokyo: asia $(zic-deps)
 	$(build-testdata)
 
+$(testdata)/XT%: testdata/XT%
+	cp $< $@
 
 $(objpfx)tzselect: tzselect.ksh $(common-objpfx)config.make
 	sed -e 's|/bin/bash|$(KSH)|g' \
diff --git a/timezone/README b/timezone/README
index 7a5e31c..2268f8e 100644
--- a/timezone/README
+++ b/timezone/README
@@ -15,3 +15,6 @@ version of the tzcode and tzdata packages.
 
 These packages may be found at ftp://ftp.iana.org/tz/releases/.  Commentary
 should be addressed to tz@iana.org.
+
+The subdirectory testdata contains manually edited data files for
+regression testing purposes.
--- /dev/null
+++ b/timezone/tst-tzset.c
@@ -0,0 +1,200 @@
+/* tzset tests with crafted time zone data.
+   Copyright (C) 2015 Free Software Foundation, Inc.
+
+   The GNU C Library is free software; you can redistribute it and/or
+   modify it under the terms of the GNU Lesser General Public
+   License as published by the Free Software Foundation; either
+   version 2.1 of the License, or (at your option) any later version.
+
+   The GNU C Library is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   Lesser General Public License for more details.
+
+   You should have received a copy of the GNU Lesser General Public
+   License along with the GNU C Library; if not, see
+   <http://www.gnu.org/licenses/>.  */
+
+#define _GNU_SOURCE 1
+
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/resource.h>
+#include <time.h>
+#include <unistd.h>
+
+static int do_test (void);
+#define TEST_FUNCTION do_test ()
+#include "../test-skeleton.c"
+
+/* Returns the name of a large TZ file.  */
+static char *
+create_tz_file (off64_t size)
+{
+  char *path;
+  int fd = create_temp_file ("tst-tzset-", &path);
+  if (fd < 0)
+    exit (1);
+
+  // Reopen for large-file support.
+  close (fd);
+  fd = open64 (path, O_WRONLY);
+  if (fd < 0)
+    {
+      printf ("open64 (%s) failed: %m\n", path);
+      exit (1);
+    }
+
+  static const char data[] = {
+    0x54, 0x5a, 0x69, 0x66, 0x32, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
+    0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
+    0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x58, 0x54, 0x47, 0x00, 0x00, 0x00,
+    0x54, 0x5a, 0x69, 0x66, 0x32, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
+    0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01,
+    0x00, 0x00, 0x00, 0x04, 0xf8, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x58, 0x54, 0x47, 0x00, 0x00,
+    0x00, 0x0a, 0x58, 0x54, 0x47, 0x30, 0x0a
+  };
+  ssize_t ret = write (fd, data, sizeof (data));
+  if (ret < 0)
+    {
+      printf ("write failed: %m\n");
+      exit (1);
+    }
+  if ((size_t) ret != sizeof (data))
+    {
+      printf ("Short write\n");
+      exit (1);
+    }
+  if (lseek64 (fd, size, SEEK_CUR) < 0)
+    {
+      printf ("lseek failed: %m\n");
+      close (fd);
+      return NULL;
+    }
+  if (write (fd, "", 1) != 1)
+    {
+      printf ("Single-byte write failed\n");
+      close (fd);
+      return NULL;
+    }
+  if (close (fd) != 0)
+    {
+      printf ("close failed: %m\n");
+      exit (1);
+    }
+  return path;
+}
+
+static void
+test_tz_file (off64_t size)
+{
+  char *path = create_tz_file (size);
+  if (setenv ("TZ", path, 1) < 0)
+    {
+      printf ("setenv failed: %m\n");
+      exit (1);
+    }
+  tzset ();
+  free (path);
+}
+
+static int
+do_test (void)
+{
+  /* Limit the size of the process.  Otherwise, some of the tests will
+     consume a lot of resources.  */
+  {
+    struct rlimit limit;
+    if (getrlimit (RLIMIT_AS, &limit) != 0)
+      {
+	printf ("getrlimit (RLIMIT_AS) failed: %m\n");
+	return 1;
+      }
+    long target = 512 * 1024 * 1024;
+    if (limit.rlim_cur == RLIM_INFINITY || limit.rlim_cur > target)
+      {
+	limit.rlim_cur = 512 * 1024 * 1024;
+	if (setrlimit (RLIMIT_AS, &limit) != 0)
+	  {
+	    printf ("setrlimit (RLIMIT_AS) failed: %m\n");
+	    return 1;
+	  }
+      }
+  }
+
+  int errors = 0;
+  for (int i = 1; i <= 4; ++i)
+    {
+      char tz[16];
+      snprintf (tz, sizeof (tz), "XT%d", i);
+      if (setenv ("TZ", tz, 1) < 0)
+	{
+	  printf ("setenv failed: %m\n");
+	  return 1;
+	}
+      tzset ();
+      if (strcmp (tzname[0], tz) == 0)
+	{
+	  printf ("Unexpected success for %s\n", tz);
+	  ++errors;
+	}
+    }
+
+  /* Large TZ files.  */
+
+  /* This will succeed on 64-bit architectures, and fail on 32-bit
+     architectures.  It used to crash on 32-bit.  */
+  test_tz_file (64 * 1024 * 1024);
+
+  /* This will fail on 64-bit and 32-bit architectures.  It used to
+     cause a test timeout on 64-bit and crash on 32-bit if the TZ file
+     open succeeded for some reason (it does not use O_LARGEFILE in
+     regular builds).  */
+  test_tz_file (4LL * 1024 * 1024 * 1024 - 6);
+
+  /* Large TZ variables.  */
+  {
+    size_t length = 64 * 1024 * 1024;
+    char *value = malloc (length + 1);
+    if (value == NULL)
+      {
+	puts ("malloc failed: %m");
+	return 1;
+      }
+    value[length] = '\0';
+
+    memset (value, ' ', length);
+    value[0] = 'U';
+    value[1] = 'T';
+    value[2] = 'C';
+    if (setenv ("TZ", value, 1) < 0)
+      {
+	printf ("setenv failed: %m\n");
+	return 1;
+      }
+    tzset ();
+
+    memset (value, '0', length);
+    value[0] = '<';
+    value[length - 1] = '>';
+    if (setenv ("TZ", value, 1) < 0)
+      {
+	printf ("setenv failed: %m\n");
+	return 1;
+      }
+    tzset ();
+  }
+
+  return errors > 0;
+}
diff --git a/timezone/Makefile b/timezone/Makefile
index 81d4a3e..bfb3463 100644
--- a/timezone/Makefile
+++ b/timezone/Makefile
@@ -113,6 +113,7 @@ $(testdata)/Asia/Tokyo: asia $(zic-deps)
 	$(build-testdata)
 
 $(testdata)/XT%: testdata/XT%
+	$(make-target-directory)
 	cp $< $@
 
 $(objpfx)tzselect: tzselect.ksh $(common-objpfx)config.make
diff -Nrup -a a/timezone/testdata/XT1 b/timezone/testdata/XT1
--- a/timezone/testdata/XT1	1969-12-31 19:00:00.000000000 -0500
+++ b/timezone/testdata/XT1	2017-09-14 10:19:11.382923956 -0400
@@ -0,0 +1,2 @@
+TZif2ÿÿÿÿXT1TZif2ÿÿÿÿøXT1
+XT10
diff -Nrup -a a/timezone/testdata/XT2 b/timezone/testdata/XT2
--- a/timezone/testdata/XT2	1969-12-31 19:00:00.000000000 -0500
+++ b/timezone/testdata/XT2	2017-09-14 10:19:11.382923956 -0400
@@ -0,0 +1,2 @@
+TZif2ÿÿÿÿXT2TZif2ÿÿÿÿøXT2
+XT20
diff -Nrup -a a/timezone/testdata/XT3 b/timezone/testdata/XT3
--- a/timezone/testdata/XT3	1969-12-31 19:00:00.000000000 -0500
+++ b/timezone/testdata/XT3	2017-09-14 10:19:11.382923956 -0400
@@ -0,0 +1,2 @@
+TZif2XT3TZif2øXT3
+XT30
diff -Nrup -a a/timezone/testdata/XT4 b/timezone/testdata/XT4
--- a/timezone/testdata/XT4	1969-12-31 19:00:00.000000000 -0500
+++ b/timezone/testdata/XT4	2017-09-14 10:19:11.383923953 -0400
@@ -0,0 +1,2 @@
+TZif2XT4TZif2øXT4
+XT40