[PATCH 1/4] Avoid use of local statics
* zic.c (inrule, inzsub, writezone): Avoid use of static when auto will do. This can help GCC catch uninitialized-variable errors. --- zic.c | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/zic.c b/zic.c index 51977b2..8653fb0 100644 --- a/zic.c +++ b/zic.c @@ -1341,7 +1341,7 @@ getsave(char *field, bool *isdst) static void inrule(char **fields, int nfields) { - static struct rule r; + struct rule r; if (nfields != RULE_FIELDS) { error(_("wrong number of fields on Rule line")); @@ -1418,7 +1418,7 @@ inzsub(char **fields, int nfields, bool iscont) { register char * cp; char * cp1; - static struct zone z; + struct zone z; register int i_stdoff, i_rule, i_format; register int i_untilyear, i_untilmonth; register int i_untilday, i_untiltime; @@ -1868,8 +1868,6 @@ writezone(const char *const name, const char *const string, char version, register FILE * fp; register ptrdiff_t i, j; register int pass; - static const struct tzhead tzh0; - static struct tzhead tzh; bool dir_checked = false; zic_t one = 1; zic_t y2038_boundary = one << 31; @@ -2000,6 +1998,7 @@ writezone(const char *const name, const char *const string, char version, for (pass = 1; pass <= 2; ++pass) { register ptrdiff_t thistimei, thistimecnt, thistimelim; register int thisleapi, thisleapcnt, thisleaplim; + struct tzhead tzh; int currenttype, thisdefaulttype; bool locut, hicut; zic_t lo; @@ -2168,7 +2167,7 @@ writezone(const char *const name, const char *const string, char version, thistimelim = thistimei; } #define DO(field) fwrite(tzh.field, sizeof tzh.field, 1, fp) - tzh = tzh0; + memset(&tzh, 0, sizeof tzh); memcpy(tzh.tzh_magic, TZ_MAGIC, sizeof tzh.tzh_magic); tzh.tzh_version[0] = version; convert(utcnt, tzh.tzh_ttisutcnt); -- 2.27.0
zic now generates a POSIX-conforming TZ string for timezones where all-year DST is predicted for the indefinite future. This implements a suggestion by Michael Deckers in: https://mm.icann.org/pipermail/tz/2020-February/028834.html This change does not affect any existing tzdb zones, as we prefer adjusting the standard time to all-year DST. * zic.c (rule_cmp): Compare two rules to be the same if they both extend into the indefinite future. (stringzone): For DST all year, generate something like "XXX3EDT4,0/0,J365/23" (which conforms to current POSIX) instead of "EST5EDT4,0/0,J365/25" (which does not). Eventually POSIX should change to allow either form, but we might as well be compatible with current POSIX when we can. --- zic.c | 127 +++++++++++++++++++++++++++++++--------------------------- 1 file changed, 67 insertions(+), 60 deletions(-) diff --git a/zic.c b/zic.c index 8653fb0..4893a32 100644 --- a/zic.c +++ b/zic.c @@ -2456,6 +2456,8 @@ rule_cmp(struct rule const *a, struct rule const *b) return 1; if (a->r_hiyear != b->r_hiyear) return a->r_hiyear < b->r_hiyear ? -1 : 1; + if (a->r_hiyear == ZIC_MAX) + return 0; if (a->r_month - b->r_month != 0) return a->r_month - b->r_month; return a->r_dayofmonth - b->r_dayofmonth; @@ -2469,12 +2471,16 @@ stringzone(char *result, struct zone const *zpfirst, ptrdiff_t zonecount) register struct rule * stdrp; register struct rule * dstrp; register ptrdiff_t i; - register const char * abbrvar; register int compat = 0; register int c; size_t len; int offsetlen; struct rule stdr, dstr; + int dstcmp; + struct rule *lastrp[2] = { NULL, NULL }; + struct zone zstr[2]; + struct zone const *stdzp; + struct zone const *dstzp; result[0] = '\0'; @@ -2484,63 +2490,64 @@ stringzone(char *result, struct zone const *zpfirst, ptrdiff_t zonecount) return -1; zp = zpfirst + zonecount - 1; - stdrp = dstrp = NULL; for (i = 0; i < zp->z_nrules; ++i) { + struct rule **last; + int cmp; rp = &zp->z_rules[i]; - if (rp->r_hiwasnum || rp->r_hiyear != ZIC_MAX) - continue; - if (!rp->r_isdst) { - if (stdrp == NULL) - stdrp = rp; - else return -1; - } else { - if (dstrp == NULL) - dstrp = rp; - else return -1; - } - } - if (stdrp == NULL && dstrp == NULL) { - /* - ** There are no rules running through "max". - ** Find the latest std rule in stdabbrrp - ** and latest rule of any type in stdrp. - */ - register struct rule *stdabbrrp = NULL; - for (i = 0; i < zp->z_nrules; ++i) { - rp = &zp->z_rules[i]; - if (!rp->r_isdst && rule_cmp(stdabbrrp, rp) < 0) - stdabbrrp = rp; - if (rule_cmp(stdrp, rp) < 0) - stdrp = rp; - } - if (stdrp != NULL && stdrp->r_isdst) { - /* Perpetual DST. */ - dstr.r_month = TM_JANUARY; - dstr.r_dycode = DC_DOM; - dstr.r_dayofmonth = 1; - dstr.r_tod = 0; - dstr.r_todisstd = dstr.r_todisut = false; - dstr.r_isdst = stdrp->r_isdst; - dstr.r_save = stdrp->r_save; - dstr.r_abbrvar = stdrp->r_abbrvar; - stdr.r_month = TM_DECEMBER; - stdr.r_dycode = DC_DOM; - stdr.r_dayofmonth = 31; - stdr.r_tod = SECSPERDAY + stdrp->r_save; - stdr.r_todisstd = stdr.r_todisut = false; - stdr.r_isdst = false; - stdr.r_save = 0; - stdr.r_abbrvar - = (stdabbrrp ? stdabbrrp->r_abbrvar : ""); - dstrp = &dstr; - stdrp = &stdr; - } - } - if (stdrp == NULL && (zp->z_nrules != 0 || zp->z_isdst)) - return -1; - abbrvar = (stdrp == NULL) ? "" : stdrp->r_abbrvar; - len = doabbr(result, zp, abbrvar, false, 0, true); - offsetlen = stringoffset(result + len, - zp->z_stdoff); + last = &lastrp[rp->r_isdst]; + cmp = rule_cmp(*last, rp); + if (cmp < 0) + *last = rp; + else if (cmp == 0) + return -1; + } + stdrp = lastrp[false]; + dstrp = lastrp[true]; + dstcmp = zp->z_nrules ? rule_cmp(dstrp, stdrp) : zp->z_isdst ? 1 : -1; + stdzp = dstzp = zp; + + if (dstcmp < 0) { + /* Standard time all year. */ + dstrp = NULL; + } else if (0 < dstcmp) { + /* DST all year. Use an abbreviation like + "XXX3EDT4,0/0,J365/23" for EDT (-04) all year. */ + zic_t save = dstrp ? dstrp->r_save : zp->z_save; + if (0 <= save) + { + /* Positive DST, the typical case for all-year DST. + Fake a timezone with negative DST. */ + stdzp = &zstr[0]; + dstzp = &zstr[1]; + zstr[0].z_stdoff = zp->z_stdoff - 2 * save; + zstr[0].z_format = "XXX"; /* Any 3 letters will do. */ + zstr[0].z_format_specifier = 0; + zstr[1].z_stdoff = zstr[0].z_stdoff; + zstr[1].z_format = zp->z_format; + zstr[1].z_format_specifier = zp->z_format_specifier; + } + dstr.r_month = TM_JANUARY; + dstr.r_dycode = DC_DOM; + dstr.r_dayofmonth = 1; + dstr.r_tod = 0; + dstr.r_todisstd = dstr.r_todisut = false; + dstr.r_isdst = true; + dstr.r_save = save < 0 ? save : -save; + dstr.r_abbrvar = dstrp ? dstrp->r_abbrvar : NULL; + stdr.r_month = TM_DECEMBER; + stdr.r_dycode = DC_DOM; + stdr.r_dayofmonth = 31; + stdr.r_tod = SECSPERDAY + dstr.r_save; + stdr.r_todisstd = stdr.r_todisut = false; + stdr.r_isdst = false; + stdr.r_save = 0; + stdr.r_abbrvar = save < 0 && stdrp ? stdrp->r_abbrvar : NULL; + dstrp = &dstr; + stdrp = &stdr; + } + len = doabbr(result, stdzp, stdrp ? stdrp->r_abbrvar : NULL, + false, 0, true); + offsetlen = stringoffset(result + len, - stdzp->z_stdoff); if (! offsetlen) { result[0] = '\0'; return -1; @@ -2548,11 +2555,11 @@ stringzone(char *result, struct zone const *zpfirst, ptrdiff_t zonecount) len += offsetlen; if (dstrp == NULL) return compat; - len += doabbr(result + len, zp, dstrp->r_abbrvar, + len += doabbr(result + len, dstzp, dstrp->r_abbrvar, dstrp->r_isdst, dstrp->r_save, true); if (dstrp->r_save != SECSPERMIN * MINSPERHOUR) { offsetlen = stringoffset(result + len, - - (zp->z_stdoff + dstrp->r_save)); + - (dstzp->z_stdoff + dstrp->r_save)); if (! offsetlen) { result[0] = '\0'; return -1; @@ -2560,7 +2567,7 @@ stringzone(char *result, struct zone const *zpfirst, ptrdiff_t zonecount) len += offsetlen; } result[len++] = ','; - c = stringrule(result + len, dstrp, dstrp->r_save, zp->z_stdoff); + c = stringrule(result + len, dstrp, dstrp->r_save, stdzp->z_stdoff); if (c < 0) { result[0] = '\0'; return -1; @@ -2569,7 +2576,7 @@ stringzone(char *result, struct zone const *zpfirst, ptrdiff_t zonecount) compat = c; len += strlen(result + len); result[len++] = ','; - c = stringrule(result + len, stdrp, dstrp->r_save, zp->z_stdoff); + c = stringrule(result + len, stdrp, dstrp->r_save, stdzp->z_stdoff); if (c < 0) { result[0] = '\0'; return -1; -- 2.27.0
* localtime.c (tzparse): Fix off-by-(stdoffset-dstoffset) bug. Without this fix, "zdump -v 'EST5EDT4,0/0,J365/25'" would dump core on some platforms. This string is an extension to POSIX, specified by Internet RFC 8536. No existing tzdb zones use all-year DST, so this bug should trigger only when TZ is explicitly set to an all-year DST string. --- NEWS | 4 ++++ localtime.c | 4 +--- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/NEWS b/NEWS index a22e8ff..8e968eb 100644 --- a/NEWS +++ b/NEWS @@ -8,6 +8,10 @@ Unreleased, experimental changes Changes to code + Fix bug that caused 'localtime' etc. to crash when TZ was + set to a all-year DST string like "EST5EDT4,0/0,J365/25" that does + not conform to POSIX but does conform to Internet RFC 8536. + Fix bug in zic -r; in some cases, the dummy time type after the last time transition disagreed with the TZ string, contrary to Internet RFC 8563 section 3.3. diff --git a/localtime.c b/localtime.c index 82a3b70..333c6ea 100644 --- a/localtime.c +++ b/localtime.c @@ -1181,9 +1181,7 @@ tzparse(const char *name, struct state *sp, bool lastditch) } if (reversed || (starttime < endtime - && (endtime - starttime - < (yearsecs - + (stdoffset - dstoffset))))) { + && endtime - starttime < yearsecs)) { if (TZ_MAX_TIMES - 2 < timecnt) break; sp->ats[timecnt] = janfirst; -- 2.27.0
* localtime.c (localsub): Redo computation of NEWT to avoid integer overflow when SECONDS is close to the maximum time_t value. Without this fix, localtime mishandles TZ="EST5EDT4,0/0,J365/0" by incorrectly omitting transitions before 1970. For example, "zdump -i 'EST5EDT4,0/0,J365/0'" incorrectly lists 1970-01-01 as the first transition date. --- NEWS | 4 ++++ localtime.c | 14 ++++++++++---- 2 files changed, 14 insertions(+), 4 deletions(-) diff --git a/NEWS b/NEWS index 8e968eb..af5dd9f 100644 --- a/NEWS +++ b/NEWS @@ -12,6 +12,10 @@ Unreleased, experimental changes set to a all-year DST string like "EST5EDT4,0/0,J365/25" that does not conform to POSIX but does conform to Internet RFC 8536. + Fix another bug that caused 'localtime' etc. to crash when TZ was + set to a POSIX-conforming but unusual TZ string like + "EST5EDT4,0/0,J365/0", where almost all the year is DST. + Fix bug in zic -r; in some cases, the dummy time type after the last time transition disagreed with the TZ string, contrary to Internet RFC 8563 section 3.3. diff --git a/localtime.c b/localtime.c index 333c6ea..b40e5e8 100644 --- a/localtime.c +++ b/localtime.c @@ -1459,7 +1459,7 @@ localsub(struct state const *sp, time_t const *timep, int_fast32_t setname, } if ((sp->goback && t < sp->ats[0]) || (sp->goahead && t > sp->ats[sp->timecnt - 1])) { - time_t newt = t; + time_t newt; register time_t seconds; register time_t years; @@ -1467,11 +1467,17 @@ localsub(struct state const *sp, time_t const *timep, int_fast32_t setname, seconds = sp->ats[0] - t; else seconds = t - sp->ats[sp->timecnt - 1]; --seconds; - years = (seconds / SECSPERREPEAT + 1) * YEARSPERREPEAT; + + /* Beware integer overflow, as SECONDS might + be close to the maximum time_t. */ + years = seconds / SECSPERREPEAT * YEARSPERREPEAT; seconds = years * AVGSECSPERYEAR; + years += YEARSPERREPEAT; if (t < sp->ats[0]) - newt += seconds; - else newt -= seconds; + newt = t + seconds + SECSPERREPEAT; + else + newt = t - seconds - SECSPERREPEAT; + if (newt < sp->ats[0] || newt > sp->ats[sp->timecnt - 1]) return NULL; /* "cannot happen" */ -- 2.27.0
participants (1)
-
Paul Eggert