diff --git a/Makefile b/Makefile index 26e8ab7..968773f 100644 --- a/Makefile +++ b/Makefile @@ -134,6 +134,8 @@ LDLIBS= # the default is system-supplied, typically "/usr/lib/locale" # -DTZDEFRULESTRING=\",date/time,date/time\" to default to the specified # DST transitions if the time zone files cannot be accessed +# -DUNINIT_TRAP=1 if reading uninitialized storage can cause problems +# other than simply getting garbage data # -DZIC_MAX_ABBR_LEN_WO_WARN=3 # (or some other number) to set the maximum time zone abbreviation length # that zic will accept without a warning (the default is 6) diff --git a/NEWS b/NEWS index 9912856..3d69b1f 100644 --- a/NEWS +++ b/NEWS @@ -59,6 +59,14 @@ Unreleased, experimental changes already defined, to make it easier to configure on common platforms. Define NO_TM_GMTOFF and NO_TM_ZONE to suppress this. + Unless the new macro UNINIT_TRAP is defined to 0, the tz code now + assumes that reading uninitialized memory yields garbage values + but does not cause other problems such as traps. + + If TM_GMTOFF is defined and UNINIT_TRAP is not 0, mktime is now + more likely to guess right for ambiguous time stamps near + transitions where tm_isdst does not change. + tzselect -c now uses a hybrid distance measure that works better in Africa. (Thanks to Alan Barrett for noting the problem.) diff --git a/localtime.c b/localtime.c index 7b22f72..e60842e 100644 --- a/localtime.c +++ b/localtime.c @@ -103,6 +103,7 @@ struct lsinfo { /* leap second information */ int_fast64_t ls_corr; /* correction to apply */ }; +#define SMALLEST(a, b) (((a) < (b)) ? (a) : (b)) #define BIGGEST(a, b) (((a) > (b)) ? (a) : (b)) #ifdef TZNAME_MAX @@ -1817,6 +1818,35 @@ time2sub(struct tm *const tmp, else lo = t; continue; } +#if defined TM_GMTOFF && ! UNINIT_TRAP + if (mytm.TM_GMTOFF != yourtm.TM_GMTOFF + && (yourtm.TM_GMTOFF < 0 + ? (-SECSPERDAY <= yourtm.TM_GMTOFF + && (mytm.TM_GMTOFF <= + (SMALLEST (INT_FAST32_MAX, LONG_MAX) + + yourtm.TM_GMTOFF))) + : (yourtm.TM_GMTOFF <= SECSPERDAY + && ((BIGGEST (INT_FAST32_MIN, LONG_MIN) + + yourtm.TM_GMTOFF) + <= mytm.TM_GMTOFF)))) { + /* MYTM matches YOURTM except with the wrong UTC offset. + YOURTM.TM_GMTOFF is plausible, so try it instead. + It's OK if YOURTM.TM_GMTOFF contains uninitialized data, + since the guess gets checked. */ + time_t altt = t; + int_fast32_t diff = mytm.TM_GMTOFF - yourtm.TM_GMTOFF; + if (!increment_overflow_time(&altt, diff)) { + struct tm alttm; + if (funcp(&altt, offset, &alttm) + && alttm.tm_isdst == mytm.tm_isdst + && alttm.TM_GMTOFF == yourtm.TM_GMTOFF + && tmcomp(&alttm, &yourtm) == 0) { + t = altt; + mytm = alttm; + } + } + } +#endif if (yourtm.tm_isdst < 0 || mytm.tm_isdst == yourtm.tm_isdst) break; /* diff --git a/private.h b/private.h index 14a9d53..31239e1 100644 --- a/private.h +++ b/private.h @@ -413,6 +413,10 @@ static time_t const time_t_max = # define INITIALIZE(x) #endif +#ifndef UNINIT_TRAP +# define UNINIT_TRAP 0 +#endif + /* ** For the benefit of GNU folk... ** '_(MSGID)' uses the current locale's message library string for MSGID.