This removes library support for the old posixrules
feature that was declared obsolete in 2019b.
Naveed8951 recently reported that the implementation has
undefined behavior when accessing contrived TZif files,
and there’s little point to fixing this.
* localtime.c (tzparse): For settings like TZ="AST4ADT"
simply default to TZDEFRULESTRING (typically current US rules)
rather than trying to load from TZDEFRULES, a feature that
has been obsolete for some time as it does not work in general.
This simplifies the code and avoids some undefined behavior
on signed integer overflow.
* newctime.3, newtzset.3: Mention this.
* tzfile.h (TZDEFRULES): Move from here ...
* zic.c (TZDEFRULES): ... to here, as only zic.c needs it now.
---
 NEWS        |   8 ++
 localtime.c | 301 ++++++++++++++++++----------------------------------
 newctime.3  |   4 +-
 newtzset.3  |  29 ++---
 tzfile.h    |  12 +--
 zic.c       |   5 +
 6 files changed, 127 insertions(+), 232 deletions(-)

diff --git a/NEWS b/NEWS
index ecccccd0..ba0b13c5 100644
--- a/NEWS
+++ b/NEWS
@@ -22,6 +22,14 @@ Unreleased, experimental changes
 
   Changes to code
 
+    localtime.c no longer accesses the posixrules file generated by
+    zic -p.  Hence for obsolete and nonconforming settings like
+    TZ="AST4ADT" it now typically falls back on US DST rules, rather
+    than attempting to override this fallback with the contents of the
+    posixrules file.  This removes library support that was declared
+    obsolete in release 2019b, and fixes some undefined behavior.
+    (Undefined behavior reported by GitHub user Naveed8951.)
+
     zic no longer generates a no-op transition when
     simultaneous Rule and Zone changes cancel each other out.
     This occurs in tzdata only in Asia/Tbilisi on 1997-03-30.
diff --git a/localtime.c b/localtime.c
index 917e957d..53a443b7 100644
--- a/localtime.c
+++ b/localtime.c
@@ -446,7 +446,7 @@ static char const *utc = etc_utc + sizeof "Etc/" - 1;
 #endif
 
 /*
-** The DST rules to use if TZ has no rules and we can't load TZDEFRULES.
+** The DST rules to use if TZ has no rules.
 ** Default to US rules as of 2017-05-07.
 ** POSIX does not specify the default DST rules;
 ** for historical reasons, US rules are a common default.
@@ -1617,7 +1617,6 @@ tzparse(const char *name, struct state *sp, struct state 
const *basep)
        int_fast32_t                    stdoffset;
        int_fast32_t                    dstoffset;
        register char *                 cp;
-       register bool                   load_ok;
        ptrdiff_t stdlen, dstlen, charcnt;
        time_t atlo = TIME_T_MIN, leaplo = TIME_T_MIN;
 
@@ -1643,18 +1642,20 @@ tzparse(const char *name, struct state *sp, struct 
state const *basep)
        if (basep) {
          if (0 < basep->timecnt)
            atlo = basep->ats[basep->timecnt - 1];
-         load_ok = false;
          sp->leapcnt = basep->leapcnt;
-         memcpy(sp->lsis, basep->lsis, sp->leapcnt * sizeof *sp->lsis);
-       } else {
-         load_ok = tzload(TZDEFRULES, sp, 0) == 0;
-         if (!load_ok)
-           sp->leapcnt = 0;    /* So, we're off a little.  */
-       }
-       if (0 < sp->leapcnt)
-         leaplo = sp->lsis[sp->leapcnt - 1].ls_trans;
+         if (0 < sp->leapcnt) {
+           memcpy(sp->lsis, basep->lsis, sp->leapcnt * sizeof *sp->lsis);
+           leaplo = sp->lsis[sp->leapcnt - 1].ls_trans;
+         }
+       } else
+         sp->leapcnt = 0;      /* So, we're off a little.  */
        sp->goback = sp->goahead = false;
        if (*name != '\0') {
+               struct rule start, end;
+               int year, yearbeg, yearlim, timecnt;
+               time_t janfirst;
+               int_fast32_t janoffset = 0;
+
                if (*name == '<') {
                        dstname = ++name;
                        name = getqzname(name, '>');
@@ -1675,194 +1676,102 @@ tzparse(const char *name, struct state *sp, struct 
state const *basep)
                        if (name == NULL)
                          return false;
                } else  dstoffset = stdoffset - SECSPERHOUR;
-               if (*name == '\0' && !load_ok)
+
+               if (*name == '\0')
                        name = TZDEFRULESTRING;
-               if (*name == ',' || *name == ';') {
-                       struct rule     start;
-                       struct rule     end;
-                       register int    year;
-                       register int    timecnt;
-                       time_t          janfirst;
-                       int_fast32_t janoffset = 0;
-                       int yearbeg, yearlim;
-
-                       ++name;
-                       if ((name = getrule(name, &start)) == NULL)
-                         return false;
-                       if (*name++ != ',')
-                         return false;
-                       if ((name = getrule(name, &end)) == NULL)
-                         return false;
-                       if (*name != '\0')
-                         return false;
-                       sp->typecnt = 2;        /* standard time and DST */
-                       /*
-                       ** Two transitions per year, from EPOCH_YEAR forward.
-                       */
-                       init_ttinfo(&sp->ttis[0], -stdoffset, false, 0);
-                       init_ttinfo(&sp->ttis[1], -dstoffset, true, stdlen + 1);
-                       timecnt = 0;
-                       janfirst = 0;
-                       yearbeg = EPOCH_YEAR;
-
-                       do {
-                         int_fast32_t yearsecs
-                           = year_lengths[isleap(yearbeg - 1)] * SECSPERDAY;
-                         time_t janfirst1 = janfirst;
-                         yearbeg--;
-                         if (increment_overflow_time(&janfirst1, -yearsecs)) {
-                           janoffset = -yearsecs;
-                           break;
-                         }
-                         janfirst = janfirst1;
-                       } while (atlo < janfirst
-                                && EPOCH_YEAR - YEARSPERREPEAT / 2 < yearbeg);
-
-                       while (true) {
-                         int_fast32_t yearsecs
-                           = year_lengths[isleap(yearbeg)] * SECSPERDAY;
-                         int yearbeg1 = yearbeg;
-                         time_t janfirst1 = janfirst;
-                         if (increment_overflow_time(&janfirst1, yearsecs)
-                             || increment_overflow(&yearbeg1, 1)
-                             || atlo <= janfirst1)
-                           break;
-                         yearbeg = yearbeg1;
-                         janfirst = janfirst1;
-                       }
+               if (! (*name == ',' || *name == ';'))
+                 return false;
 
-                       yearlim = yearbeg;
-                       if (increment_overflow(&yearlim, years_of_observations))
-                         yearlim = INT_MAX;
-                       for (year = yearbeg; year < yearlim; year++) {
-                               int_fast32_t
-                                 starttime = transtime(year, &start, 
stdoffset),
-                                 endtime = transtime(year, &end, dstoffset);
-                               int_fast32_t
-                                 yearsecs = (year_lengths[isleap(year)]
-                                             * SECSPERDAY);
-                               bool reversed = endtime < starttime;
-                               if (reversed) {
-                                       int_fast32_t swap = starttime;
-                                       starttime = endtime;
-                                       endtime = swap;
-                               }
-                               if (reversed
-                                   || (starttime < endtime
-                                       && endtime - starttime < yearsecs)) {
-                                       if (TZ_MAX_TIMES - 2 < timecnt)
-                                               break;
-                                       sp->ats[timecnt] = janfirst;
-                                       if (! increment_overflow_time
-                                           (&sp->ats[timecnt],
-                                            janoffset + starttime)
-                                           && atlo <= sp->ats[timecnt])
-                                         sp->types[timecnt++] = !reversed;
-                                       sp->ats[timecnt] = janfirst;
-                                       if (! increment_overflow_time
-                                           (&sp->ats[timecnt],
-                                            janoffset + endtime)
-                                           && atlo <= sp->ats[timecnt]) {
-                                         sp->types[timecnt++] = reversed;
-                                       }
-                               }
-                               if (endtime < leaplo) {
-                                 yearlim = year;
-                                 if (increment_overflow(&yearlim,
-                                                        years_of_observations))
-                                   yearlim = INT_MAX;
-                               }
-                               if (increment_overflow_time
-                                   (&janfirst, janoffset + yearsecs))
-                                       break;
-                               janoffset = 0;
-                       }
-                       sp->timecnt = timecnt;
-                       if (! timecnt) {
-                               sp->ttis[0] = sp->ttis[1];
-                               sp->typecnt = 1;        /* Perpetual DST.  */
-                       } else if (years_of_observations <= year - yearbeg)
-                               sp->goback = sp->goahead = true;
-               } else {
-                       register int_fast32_t   theirstdoffset;
-                       register int_fast32_t   theirdstoffset;
-                       register int_fast32_t   theiroffset;
-                       register bool           isdst;
-                       register int            i;
-                       register int            j;
-
-                       if (*name != '\0')
-                         return false;
-                       /*
-                       ** Initial values of theirstdoffset and theirdstoffset.
-                       */
-                       theirstdoffset = 0;
-                       for (i = 0; i < sp->timecnt; ++i) {
-                               j = sp->types[i];
-                               if (!sp->ttis[j].tt_isdst) {
-                                       theirstdoffset =
-                                               - sp->ttis[j].tt_utoff;
-                                       break;
-                               }
-                       }
-                       theirdstoffset = 0;
-                       for (i = 0; i < sp->timecnt; ++i) {
-                               j = sp->types[i];
-                               if (sp->ttis[j].tt_isdst) {
-                                       theirdstoffset =
-                                               - sp->ttis[j].tt_utoff;
-                                       break;
-                               }
-                       }
-                       /*
-                       ** Initially we're assumed to be in standard time.
-                       */
-                       isdst = false;
-                       /*
-                       ** Now juggle transition times and types
-                       ** tracking offsets as you do.
-                       */
-                       for (i = 0; i < sp->timecnt; ++i) {
-                               j = sp->types[i];
-                               sp->types[i] = sp->ttis[j].tt_isdst;
-                               if (sp->ttis[j].tt_ttisut) {
-                                       /* No adjustment to transition time */
-                               } else {
-                                       /*
-                                       ** If daylight saving time is in
-                                       ** effect, and the transition time was
-                                       ** not specified as standard time, add
-                                       ** the daylight saving time offset to
-                                       ** the transition time; otherwise, add
-                                       ** the standard time offset to the
-                                       ** transition time.
-                                       */
-                                       /*
-                                       ** Transitions from DST to DDST
-                                       ** will effectively disappear since
-                                       ** proleptic TZ strings have only one
-                                       ** DST offset.
-                                       */
-                                       if (isdst && !sp->ttis[j].tt_ttisstd) {
-                                               sp->ats[i] += dstoffset -
-                                                       theirdstoffset;
-                                       } else {
-                                               sp->ats[i] += stdoffset -
-                                                       theirstdoffset;
-                                       }
-                               }
-                               theiroffset = -sp->ttis[j].tt_utoff;
-                               if (sp->ttis[j].tt_isdst)
-                                       theirdstoffset = theiroffset;
-                               else    theirstdoffset = theiroffset;
-                       }
-                       /*
-                       ** Finally, fill in ttis.
-                       */
-                       init_ttinfo(&sp->ttis[0], -stdoffset, false, 0);
-                       init_ttinfo(&sp->ttis[1], -dstoffset, true, stdlen + 1);
-                       sp->typecnt = 2;
+               name = getrule(name + 1, &start);
+               if (!name)
+                 return false;
+               if (*name++ != ',')
+                 return false;
+               name = getrule(name, &end);
+               if (!name || *name)
+                 return false;
+               sp->typecnt = 2;        /* standard time and DST */
+               /*
+               ** Two transitions per year, from EPOCH_YEAR forward.
+               */
+               init_ttinfo(&sp->ttis[0], -stdoffset, false, 0);
+               init_ttinfo(&sp->ttis[1], -dstoffset, true, stdlen + 1);
+               timecnt = 0;
+               janfirst = 0;
+               yearbeg = EPOCH_YEAR;
+
+               do {
+                 int_fast32_t yearsecs
+                   = year_lengths[isleap(yearbeg - 1)] * SECSPERDAY;
+                 time_t janfirst1 = janfirst;
+                 yearbeg--;
+                 if (increment_overflow_time(&janfirst1, -yearsecs)) {
+                   janoffset = -yearsecs;
+                   break;
+                 }
+                 janfirst = janfirst1;
+               } while (atlo < janfirst
+                        && EPOCH_YEAR - YEARSPERREPEAT / 2 < yearbeg);
+
+               while (true) {
+                 int_fast32_t yearsecs
+                   = year_lengths[isleap(yearbeg)] * SECSPERDAY;
+                 int yearbeg1 = yearbeg;
+                 time_t janfirst1 = janfirst;
+                 if (increment_overflow_time(&janfirst1, yearsecs)
+                     || increment_overflow(&yearbeg1, 1)
+                     || atlo <= janfirst1)
+                   break;
+                 yearbeg = yearbeg1;
+                 janfirst = janfirst1;
+               }
+
+               yearlim = yearbeg;
+               if (increment_overflow(&yearlim, years_of_observations))
+                 yearlim = INT_MAX;
+               for (year = yearbeg; year < yearlim; year++) {
+                 int_fast32_t
+                   starttime = transtime(year, &start, stdoffset),
+                   endtime = transtime(year, &end, dstoffset),
+                   yearsecs = year_lengths[isleap(year)] * SECSPERDAY;
+                 bool reversed = endtime < starttime;
+                 if (reversed) {
+                   int_fast32_t swap = starttime;
+                   starttime = endtime;
+                   endtime = swap;
+                 }
+                 if (reversed
+                     || (starttime < endtime
+                         && endtime - starttime < yearsecs)) {
+                   if (TZ_MAX_TIMES - 2 < timecnt)
+                     break;
+                   sp->ats[timecnt] = janfirst;
+                   if (! increment_overflow_time(&sp->ats[timecnt],
+                                                 janoffset + starttime)
+                       && atlo <= sp->ats[timecnt])
+                     sp->types[timecnt++] = !reversed;
+                   sp->ats[timecnt] = janfirst;
+                   if (! increment_overflow_time(&sp->ats[timecnt],
+                                                 janoffset + endtime)
+                       && atlo <= sp->ats[timecnt]) {
+                     sp->types[timecnt++] = reversed;
+                   }
+                 }
+                 if (endtime < leaplo) {
+                   yearlim = year;
+                   if (increment_overflow(&yearlim, years_of_observations))
+                     yearlim = INT_MAX;
+                 }
+                 if (increment_overflow_time(&janfirst, janoffset + yearsecs))
+                   break;
+                 janoffset = 0;
                }
+               sp->timecnt = timecnt;
+               if (! timecnt) {
+                 sp->ttis[0] = sp->ttis[1];
+                 sp->typecnt = 1;      /* Perpetual DST.  */
+               } else if (years_of_observations <= year - yearbeg)
+                 sp->goback = sp->goahead = true;
        } else {
                dstlen = 0;
                sp->typecnt = 1;                /* only standard time */
diff --git a/newctime.3 b/newctime.3
index 8c611bd0..a8779e6a 100644
--- a/newctime.3
+++ b/newctime.3
@@ -311,13 +311,11 @@ and
 functions might (or might not) also behave this way.
 This is for compatibility with older platforms, as required by POSIX.
 .SH FILES
-.ta \w'/usr/share/zoneinfo/posixrules\0\0'u
+.ta \w'/usr/share/zoneinfo/GMT\0\0'u
 /etc/localtime local timezone file
 .br
 /usr/share/zoneinfo    timezone directory
 .br
-/usr/share/zoneinfo/posixrules default DST rules (obsolete)
-.br
 /usr/share/zoneinfo/GMT        for UTC leap seconds
 .PP
 If /usr/share/zoneinfo/GMT is absent,
diff --git a/newtzset.3 b/newtzset.3
index 028cfd25..77e76c9d 100644
--- a/newtzset.3
+++ b/newtzset.3
@@ -277,7 +277,7 @@ is a placeholder.
 .TP
 .B <\-03>3<\-02>,M3.5.0/\-2,M10.5.0/\-1
 stands for time in western Greenland, 3 hours behind UT, where clocks
-follow the EU rules of
+follow the EU rule of
 springing forward on March's last Sunday at 01:00 UT (\-02:00 local
 time, i.e., 22:00 the previous day) and falling back on October's last
 Sunday at 01:00 UT (\-01:00 local time, i.e., 23:00 the previous day).
@@ -290,28 +290,13 @@ If
 .I TZ
 specifies daylight saving time but does not specify a
 .IR rule ,
-and the optional
-.BR tzfile (5)-format
-file
-.B posixrules
-is present in the system time conversion information directory, the
-rules in
-.B posixrules
-are used, with the
-.B posixrules
-standard and daylight saving time offsets from UT
-replaced by those specified by the
-.I offset
-values in
-.IR TZ .
-However, the
-.B posixrules
-file is obsolete: if it is present it is only for backward compatibility,
-and it does not work reliably.
+the rule typically defaults to the current US daylight-saving rule,
+although such a default is not guaranteed and
+is incorrect for many locations outside the US.
 Therefore, if a
 .I TZ
 string directly specifies a timezone with daylight saving time,
-it should specify the daylight saving rules explicitly.
+it should specify the daylight saving rule explicitly.
 .PP
 For compatibility with System V Release 3.1, a semicolon
 .RB ( ; )
@@ -403,13 +388,11 @@ for any of the errors specified for the routines
 and
 .BR read (2).
 .SH FILES
-.ta \w'/usr/share/zoneinfo/posixrules\0\0'u
+.ta \w'/usr/share/zoneinfo/GMT\0\0'u
 /etc/localtime local timezone file
 .br
 /usr/share/zoneinfo    timezone directory
 .br
-/usr/share/zoneinfo/posixrules default DST rules (obsolete)
-.br
 /usr/share/zoneinfo/GMT        for UTC leap seconds
 .PP
 If /usr/share/zoneinfo/GMT is absent,
diff --git a/tzfile.h b/tzfile.h
index b00eb9e6..d1f0fbe6 100644
--- a/tzfile.h
+++ b/tzfile.h
@@ -17,16 +17,8 @@
 ** Thank you!
 */
 
-/*
-** Information about time zone files.
-*/
-
-#ifndef TZDEFRULES
-# define TZDEFRULES "posixrules"
-#endif /* !defined TZDEFRULES */
-
-
-/* See Internet RFC 9636 for more details about the following format.  */
+/* Information about time zone files.
+   See Internet RFC 9636 for more details about the following format.  */
 
 /*
 ** Each file begins with. . .
diff --git a/zic.c b/zic.c
index 6203a75b..73155138 100644
--- a/zic.c
+++ b/zic.c
@@ -164,6 +164,11 @@ static uid_t output_owner = -1;
 # include <stdalign.h>
 #endif
 
+/* The name used for the file implementing the obsolete -p option.  */
+#ifndef TZDEFRULES
+# define TZDEFRULES "posixrules"
+#endif
+
 /* The maximum length of a text line, including the trailing newline.  */
 #ifndef _POSIX2_LINE_MAX
 # define _POSIX2_LINE_MAX 2048
-- 
2.52.0

Reply via email to