
This also pacifies -Wsuggest-attribute=pure on x86 on platforms that lack tm_gmtoff and tm_zone. * zdump.c (xstrsize): Remove; no longer used. (tzalloc, saveabbr): Use plain strlen here, since the string sizes have already been checked. (main): Limit ‘longest’ to INT_MAX - 2 so that a printf with it and two spaces does not flake out. Check for arglen overflow once here, rather than on every alloc. --- zdump.c | 24 ++++++++---------------- 1 file changed, 8 insertions(+), 16 deletions(-) diff --git a/zdump.c b/zdump.c index e8178733..b668022d 100644 --- a/zdump.c +++ b/zdump.c @@ -148,17 +148,6 @@ sumsize(ptrdiff_t a, ptrdiff_t b) size_overflow(); } -/* Return the size of of the string STR, including its trailing NUL. - Report an error and exit if this would exceed INDEX_MAX which means - pointer subtraction wouldn't work. */ -static ptrdiff_t -xstrsize(char const *str) -{ - size_t len = strlen(str); - if (len < INDEX_MAX) - return len + 1; - size_overflow(); -} /* Return a pointer to a newly allocated buffer of size SIZE, exiting on failure. SIZE should be positive. */ @@ -266,7 +255,7 @@ tzalloc(char const *val) static ptrdiff_t fakeenv0size; void *freeable = NULL; char **env = fakeenv, **initial_environ; - ptrdiff_t valsize = xstrsize(val); + ptrdiff_t valsize = strlen(val) + 1; if (fakeenv0size < valsize) { char **e = environ, **to; ptrdiff_t initial_nenvptrs = 1; /* Counting the trailing NULL pointer. */ @@ -425,7 +414,7 @@ saveabbr(char **buf, ptrdiff_t *bufalloc, struct tm const *tmp) if (HAVE_LOCALTIME_RZ) return ab; else { - ptrdiff_t absize = xstrsize(ab); + ptrdiff_t absize = strlen(ab) + 1; if (*bufalloc < absize) { free(*buf); @@ -487,6 +476,7 @@ main(int argc, char *argv[]) register time_t cuthitime; time_t now; bool iflag = false; + size_t arglenmax = 0; cutlotime = absolute_min_time; cuthitime = absolute_max_time; @@ -586,12 +576,14 @@ main(int argc, char *argv[]) now = time(NULL); now |= !now; } - longest = 0; for (i = optind; i < argc; i++) { size_t arglen = strlen(argv[i]); - if (longest < arglen) - longest = min(arglen, INT_MAX); + if (arglenmax < arglen) + arglenmax = arglen; } + if (!HAVE_SETENV && INDEX_MAX <= arglenmax) + size_overflow(); + longest = min(arglenmax, INT_MAX - 2); for (i = optind; i < argc; ++i) { timezone_t tz = tzalloc(argv[i]); -- 2.47.0

* private.h (_FILE_OFFSET_BITS): Do not define if _TIME_BITS is already defined, as it is not necessary to use 64-bit off_t if the builder has asked for 32-bit time_t. --- private.h | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/private.h b/private.h index 7a3b3991..fcec70ca 100644 --- a/private.h +++ b/private.h @@ -162,11 +162,13 @@ setting _TIME_BITS to 64 does not work. The code does not otherwise rely on _FILE_OFFSET_BITS being 64, since it does not use off_t or functions like 'stat' that depend on off_t. */ -#ifndef _FILE_OFFSET_BITS -# define _FILE_OFFSET_BITS 64 -#endif -#if !defined _TIME_BITS && _FILE_OFFSET_BITS == 64 -# define _TIME_BITS 64 +#ifndef _TIME_BITS +# ifndef _FILE_OFFSET_BITS +# define _FILE_OFFSET_BITS 64 +# endif +# if _FILE_OFFSET_BITS == 64 +# define _TIME_BITS 64 +# endif #endif /* -- 2.47.0

This is sometimes needed with ‘make check_time_t_alternatives’. * private.h (ATTRIBUTE_UNSEQUENCED, ATTRIBUTE_CONST): New macros. (difftime): Declare with ATTRIBUTE_CONST. --- private.h | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/private.h b/private.h index fcec70ca..9de450c0 100644 --- a/private.h +++ b/private.h @@ -557,13 +557,26 @@ typedef unsigned long uintmax_t; # define ATTRIBUTE_REPRODUCIBLE /* empty */ #endif +#if HAVE___HAS_C_ATTRIBUTE +# if __has_c_attribute(unsequenced) +# define ATTRIBUTE_UNSEQUENCED [[unsequenced]] +# endif +#endif +#ifndef ATTRIBUTE_UNSEQUENCED +# define ATTRIBUTE_UNSEQUENCED /* empty */ +#endif + /* GCC attributes that are useful in tzcode. + __attribute__((const)) is stricter than [[unsequenced]], + so the latter is an adequate substitute in non-GCC C23 platforms. __attribute__((pure)) is stricter than [[reproducible]], so the latter is an adequate substitute in non-GCC C23 platforms. */ #if __GNUC__ < 3 +# define ATTRIBUTE_CONST ATTRIBUTE_UNSEQUENCED # define ATTRIBUTE_FORMAT(spec) /* empty */ # define ATTRIBUTE_PURE ATTRIBUTE_REPRODUCIBLE #else +# define ATTRIBUTE_CONST __attribute__((const)) # define ATTRIBUTE_FORMAT(spec) __attribute__((format spec)) # define ATTRIBUTE_PURE __attribute__((pure)) #endif @@ -708,7 +721,7 @@ DEPRECATED_IN_C23 char *ctime(time_t const *); char *asctime_r(struct tm const *restrict, char *restrict); char *ctime_r(time_t const *, char *); #endif -double difftime(time_t, time_t); +ATTRIBUTE_CONST double difftime(time_t, time_t); size_t strftime(char *restrict, size_t, char const *restrict, struct tm const *restrict); # if HAVE_STRFTIME_L -- 2.47.0

* localtime.c (increment_overflow_iinntt): New function. (normalize_overflow_iinntt): Rename from normalize_overflow32, and operate on iinntt rather than on int_fast32_t. Caller changed. (time2sub): Use iinntt not int_fast32_t for year, to avoid integer overflow when adding 1900 to tm_year. * private.h (iinntt): New type, which must be wider than int. (IINNTT_MIN, IINNTT_MAX): New macros. --- localtime.c | 33 +++++++++++++++++++++++---------- private.h | 17 +++++++++++++++++ 2 files changed, 40 insertions(+), 10 deletions(-) diff --git a/localtime.c b/localtime.c index f331473e..1703d500 100644 --- a/localtime.c +++ b/localtime.c @@ -151,7 +151,7 @@ static struct tm *gmtsub(struct state const *, time_t const *, int_fast32_t, static bool increment_overflow(int *, int); static bool increment_overflow_time(time_t *, int_fast32_t); static int_fast32_t leapcorr(struct state const *, time_t); -static bool normalize_overflow32(int_fast32_t *, int *, int); +static bool normalize_overflow_iinntt(iinntt *, int *, int); static struct tm *timesub(time_t const *, int_fast32_t, struct state const *, struct tm *); static bool tzparse(char const *, struct state *, struct state const *); @@ -1818,6 +1818,19 @@ increment_overflow32(int_fast32_t *const lp, int const m) #endif } +static bool +increment_overflow_iinntt(iinntt *lp, int m) +{ +#ifdef ckd_add + return ckd_add(lp, *lp, m); +#else + if (*lp < 0 ? m < IINNTT_MIN - *lp : IINNTT_MAX - *lp < m) + return true; + *lp += m; + return false; +#endif +} + static bool increment_overflow_time(time_t *tp, int_fast32_t j) { @@ -1851,7 +1864,7 @@ normalize_overflow(int *const tensptr, int *const unitsptr, const int base) } static bool -normalize_overflow32(int_fast32_t *tensptr, int *unitsptr, int base) +normalize_overflow_iinntt(iinntt *tensptr, int *unitsptr, int base) { register int tensdelta; @@ -1859,7 +1872,7 @@ normalize_overflow32(int_fast32_t *tensptr, int *unitsptr, int base) (*unitsptr / base) : (-1 - (-1 - *unitsptr) / base); *unitsptr -= tensdelta * base; - return increment_overflow32(tensptr, tensdelta); + return increment_overflow_iinntt(tensptr, tensdelta); } static int @@ -1910,7 +1923,7 @@ time2sub(struct tm *const tmp, register int_fast32_t li; register time_t lo; register time_t hi; - int_fast32_t y; + iinntt y; time_t newt; time_t t; struct tm yourtm, mytm; @@ -1928,16 +1941,16 @@ time2sub(struct tm *const tmp, if (normalize_overflow(&yourtm.tm_mday, &yourtm.tm_hour, HOURSPERDAY)) return WRONG; y = yourtm.tm_year; - if (normalize_overflow32(&y, &yourtm.tm_mon, MONSPERYEAR)) + if (normalize_overflow_iinntt(&y, &yourtm.tm_mon, MONSPERYEAR)) return WRONG; /* ** Turn y into an actual year number for now. ** It is converted back to an offset from TM_YEAR_BASE later. */ - if (increment_overflow32(&y, TM_YEAR_BASE)) + if (increment_overflow_iinntt(&y, TM_YEAR_BASE)) return WRONG; while (yourtm.tm_mday <= 0) { - if (increment_overflow32(&y, -1)) + if (increment_overflow_iinntt(&y, -1)) return WRONG; li = y + (1 < yourtm.tm_mon); yourtm.tm_mday += year_lengths[isleap(li)]; @@ -1945,7 +1958,7 @@ time2sub(struct tm *const tmp, while (yourtm.tm_mday > DAYSPERLYEAR) { li = y + (1 < yourtm.tm_mon); yourtm.tm_mday -= year_lengths[isleap(li)]; - if (increment_overflow32(&y, 1)) + if (increment_overflow_iinntt(&y, 1)) return WRONG; } for ( ; ; ) { @@ -1955,7 +1968,7 @@ time2sub(struct tm *const tmp, yourtm.tm_mday -= i; if (++yourtm.tm_mon >= MONSPERYEAR) { yourtm.tm_mon = 0; - if (increment_overflow32(&y, 1)) + if (increment_overflow_iinntt(&y, 1)) return WRONG; } } @@ -1963,7 +1976,7 @@ time2sub(struct tm *const tmp, if (ckd_add(&yourtm.tm_year, y, -TM_YEAR_BASE)) return WRONG; #else - if (increment_overflow32(&y, -TM_YEAR_BASE)) + if (increment_overflow_iinntt(&y, -TM_YEAR_BASE)) return WRONG; if (! (INT_MIN <= y && y <= INT_MAX)) return WRONG; diff --git a/private.h b/private.h index 9de450c0..730786dc 100644 --- a/private.h +++ b/private.h @@ -450,6 +450,23 @@ typedef unsigned long uintmax_t; #endif /* PORT_TO_C89 */ +/* A signed type wider than int, so that we can add 1900 to tm_year + without overflow. */ +#if INT_MAX < LONG_MAX +typedef long iinntt; +# define IINNTT_MIN LONG_MIN +# define IINNTT_MAX LONG_MAX +#elif INT_MAX < LLONG_MAX +typedef long long iinntt; +# define IINNTT_MIN LLONG_MIN +# define IINNTT_MAX LLONG_MAX +#else +typedef intmax_t iinntt; +# define IINNTT_MIN INTMAX_MIN +# define IINNTT_MAX INTMAX_MAX +#endif +static_assert(IINNTT_MIN < INT_MIN && INT_MAX < IINNTT_MAX); + /* The maximum size of any created object, as a signed integer. Although the C standard does not outright prohibit larger objects, behavior is undefined if the result of pointer subtraction does not -- 2.47.0

* localtime.c (increment_overflow32): Remove. (daylight) [TZ_TIME_T && !USG_COMPAT]: (altzone) [TZ_TIME_T && !ALTZONE]: Remove no-longer-used macros. (time) [TZ_TIME_T]: Use increment_overflow_iinntt instead of increment_overflow32 to detect overflow. This simplifies the support for AmigaOS. Also, do not look at 'daylight' or 'altzone' as they are not relevant for AmigaOS, and 'daylight' was being misused anyway. --- localtime.c | 24 +++--------------------- 1 file changed, 3 insertions(+), 21 deletions(-) diff --git a/localtime.c b/localtime.c index 1703d500..f14ee80c 100644 --- a/localtime.c +++ b/localtime.c @@ -1803,21 +1803,6 @@ increment_overflow(int *ip, int j) #endif } -static bool -increment_overflow32(int_fast32_t *const lp, int const m) -{ -#ifdef ckd_add - return ckd_add(lp, *lp, m); -#else - register int_fast32_t const l = *lp; - - if ((l >= 0) ? (m > INT_FAST32_MAX - l) : (m < INT_FAST32_MIN - l)) - return true; - *lp += m; - return false; -#endif -} - static bool increment_overflow_iinntt(iinntt *lp, int m) { @@ -2392,12 +2377,8 @@ posix2time(time_t t) #if TZ_TIME_T # if !USG_COMPAT -# define daylight 0 # define timezone 0 # endif -# if !ALTZONE -# define altzone 0 -# endif /* Convert from the underlying system's time_t to the ersatz time_tz, which is called 'time_t' in this file. Typically, this merely @@ -2415,8 +2396,9 @@ time(time_t *p) { time_t r = sys_time(0); if (r != (time_t) -1) { - int_fast32_t offset = EPOCH_LOCAL ? (daylight ? timezone : altzone) : 0; - if (increment_overflow32(&offset, -EPOCH_OFFSET) + iinntt offset = EPOCH_LOCAL ? timezone : 0; + if (increment_overflow_iinntt(&offset, -EPOCH_OFFSET) + || ! (INT_FAST32_MIN <= offset && offset <= INT_FAST32_MAX) || increment_overflow_time(&r, offset)) { errno = EOVERFLOW; r = -1; -- 2.47.0

* Makefile (strftime.o): Depend on localtime.c. * NEWS: Mention this. * localtime.c (USE_TIMEX_T): Provide default for new macro. (timex_t) [USE_TIMEX_T]: New type. (time_t, TIME_T_MIN, TIME_T_MAX) [USE_TIMEX_T]: (timeoff) [USE_TIMEX_T && TM_GMTOFF]: #define to timex_timeoff. (mktime) [USE_TIMEX_T && !TM_GMTOFF]: #define to timex_mktime. (EXTERN_TIMEOFF) [USE_TIMEX_T && TM_GMTOFF]: (utc, lcl_TZname, lcl_is_set, tm, tzname, timezone, daylight) (altzone, update_tzname_etc, may_update_tzname_etc, settzname) (scrub_abbrs, zoneinit, tzset_unlocked, tzset, tzalloc, tzfree) (localsub, localtime_rz, localtime_tzset, localtime, localtime_r) (gmtime_r, gmtime, offtime, mktime_tzname, mktime_z, timelocal) (timeoff, timegm, time2posix_z, time2posix, posix2time_z) (posix2time, time): Define only if still used even when USE_TIMEX_T. (mktime, timeoff) [USE_TIMEX_T]: Now static, because these are now actually timex_mktime and timex_timeoff and are local to strftime.c. * private.h (defined_time_t, MKTIME_FITS_IN, MKTIME_MIGHT_OVERFLOW): New macros. (timeoff) [USE_TIMEX_T]: Do not #define to tz_private_timeoff as it should be defined to timex_timeoff. * strftime.c [!MKTIME_MIGHT_OVERFLOW]: #define USE_TIMEX_T and include localtime.c. * newstrftime.3: Document the improved behavior. --- Makefile | 3 +- NEWS | 7 ++ localtime.c | 208 ++++++++++++++++++++++++++++++++++---------------- newstrftime.3 | 15 ++-- private.h | 57 +++++++++++++- strftime.c | 15 +++- 6 files changed, 226 insertions(+), 79 deletions(-) diff --git a/Makefile b/Makefile index 6e623eb0..01db50d6 100644 --- a/Makefile +++ b/Makefile @@ -255,6 +255,7 @@ LDLIBS= # -DHAVE_UNISTD_H=0 if <unistd.h> does not work* # -DHAVE_UTMPX_H=0 if <utmpx.h> does not work* # -Dlocale_t=XXX if your system uses XXX instead of locale_t +# -DMKTIME_MIGHT_OVERFLOW if mktime might fail due to time_t overflow # -DPORT_TO_C89 if tzcode should also run on mostly-C89 platforms+ # Typically it is better to use a later standard. For example, # with GCC 4.9.4 (2016), prefer '-std=gnu11' to '-DPORT_TO_C89'. @@ -1358,7 +1359,7 @@ asctime.o: private.h tzfile.h date.o: private.h difftime.o: private.h localtime.o: private.h tzfile.h tzdir.h -strftime.o: private.h tzfile.h +strftime.o: localtime.c private.h tzfile.h zdump.o: version.h zic.o: private.h tzfile.h tzdir.h version.h diff --git a/NEWS b/NEWS index d1fc28f6..5ed2f9f5 100644 --- a/NEWS +++ b/NEWS @@ -26,6 +26,13 @@ Unreleased, experimental changes Changes to code + strftime %s now generates the correct numeric string even when the + represented number does not fit into time_t. This is better than + generating the numeric equivalent of (time_t) -1, as strftime did + in TZDB releases 96a (when %s was introduced) through 2020a and in + releases 2022b through 2024b. It is also better than failing and + returning 0, as strftime did in releases 2020b through 2022a. + strftime now outputs an invalid conversion specifier as-is, instead of eliding the leading '%', which confused debugging. diff --git a/localtime.c b/localtime.c index f14ee80c..07ce41b8 100644 --- a/localtime.c +++ b/localtime.c @@ -29,6 +29,46 @@ static int lock(void) { return 0; } static void unlock(void) { } #endif +/* On platforms where offtime or mktime might overflow, + strftime.c defines USE_TIMEX_T to be true and includes us. + This tells us to #define time_t to an internal type timex_t that is + wide enough so that strftime %s never suffers from integer overflow, + and to #define offtime (if TM_GMTOFF is defined) or mktime (otherwise) + to a static function that returns the redefined time_t. + It also tells us to define only data and code needed + to support the offtime or mktime variant. */ +#ifndef USE_TIMEX_T +# define USE_TIMEX_T false +#endif +#if USE_TIMEX_T +# undef TIME_T_MIN +# undef TIME_T_MAX +# undef time_t +# define time_t timex_t +# if MKTIME_FITS_IN(LONG_MIN, LONG_MAX) +typedef long timex_t; +# define TIME_T_MIN LONG_MIN +# define TIME_T_MAX LONG_MAX +# elif MKTIME_FITS_IN(LLONG_MIN, LLONG_MAX) +typedef long long timex_t; +# define TIME_T_MIN LLONG_MIN +# define TIME_T_MAX LLONG_MAX +# else +typedef intmax_t timex_t; +# define TIME_T_MIN INTMAX_MIN +# define TIME_T_MAX INTMAX_MAX +# endif + +# ifdef TM_GMTOFF +# undef timeoff +# define timeoff timex_timeoff +# undef EXTERN_TIMEOFF +# else +# undef mktime +# define mktime timex_mktime +# endif +#endif + #ifndef TZ_ABBR_CHAR_SET # define TZ_ABBR_CHAR_SET \ "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789 :+-._" @@ -72,7 +112,10 @@ static void unlock(void) { } static const char wildabbr[] = WILDABBR; static char const etc_utc[] = "Etc/UTC"; + +#if defined TM_ZONE || ((!USE_TIMEX_T || !defined TM_GMTOFF) && defined TZ_NAME) 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. @@ -172,8 +215,10 @@ static struct state *const gmtptr = &gmtmem; # define TZ_STRLEN_MAX 255 #endif /* !defined TZ_STRLEN_MAX */ +#if !USE_TIMEX_T || !defined TM_GMTOFF static char lcl_TZname[TZ_STRLEN_MAX + 1]; static int lcl_is_set; +#endif /* ** Section 4.12.3 of X3.159-1989 requires that @@ -187,22 +232,26 @@ static int lcl_is_set; ** trigger latent bugs in programs. */ -#if SUPPORT_C89 +#if !USE_TIMEX_T + +# if SUPPORT_C89 static struct tm tm; #endif -#if 2 <= HAVE_TZNAME + TZ_TIME_T +# if 2 <= HAVE_TZNAME + TZ_TIME_T char * tzname[2] = { (char *) wildabbr, (char *) wildabbr }; -#endif -#if 2 <= USG_COMPAT + TZ_TIME_T +# endif +# if 2 <= USG_COMPAT + TZ_TIME_T long timezone; int daylight; -#endif -#if 2 <= ALTZONE + TZ_TIME_T +# endif +# if 2 <= ALTZONE + TZ_TIME_T long altzone; +# endif + #endif /* Initialize *S to a value based on UTOFF, ISDST, and DESIGIDX. */ @@ -271,20 +320,22 @@ detzcode64(const char *const codep) return result; } +#if !USE_TIMEX_T || !defined TM_GMTOFF + static void update_tzname_etc(struct state const *sp, struct ttinfo const *ttisp) { -#if HAVE_TZNAME +# if HAVE_TZNAME tzname[ttisp->tt_isdst] = (char *) &sp->chars[ttisp->tt_desigidx]; -#endif -#if USG_COMPAT +# endif +# if USG_COMPAT if (!ttisp->tt_isdst) timezone = - ttisp->tt_utoff; -#endif -#if ALTZONE +# endif +# if ALTZONE if (ttisp->tt_isdst) altzone = - ttisp->tt_utoff; -#endif +# endif } /* If STDDST_MASK indicates that SP's TYPE provides useful info, @@ -315,18 +366,18 @@ settzname(void) When STDDST_MASK becomes zero we can stop looking. */ int stddst_mask = 0; -#if HAVE_TZNAME +# if HAVE_TZNAME tzname[0] = tzname[1] = (char *) (sp ? wildabbr : utc); stddst_mask = 3; -#endif -#if USG_COMPAT +# endif +# if USG_COMPAT timezone = 0; stddst_mask = 3; -#endif -#if ALTZONE +# endif +# if ALTZONE altzone = 0; stddst_mask |= 2; -#endif +# endif /* ** And to get the latest time zone abbreviations into tzname. . . */ @@ -336,9 +387,9 @@ settzname(void) for (i = sp->typecnt - 1; stddst_mask && 0 <= i; i--) stddst_mask = may_update_tzname_etc(stddst_mask, sp, i); } -#if USG_COMPAT +# if USG_COMPAT daylight = stddst_mask >> 1 ^ 1; -#endif +# endif } /* Replace bogus characters in time zone abbreviations. @@ -365,6 +416,8 @@ scrub_abbrs(struct state *sp) return 0; } +#endif + /* Input buffer for data read from a compiled tz file. */ union input_buffer { /* The first part of the buffer, interpreted as a header. */ @@ -1307,6 +1360,8 @@ gmtload(struct state *const sp) tzparse("UTC0", sp, NULL); } +#if !USE_TIMEX_T || !defined TM_GMTOFF + /* Initialize *SP to a value appropriate for the TZ setting NAME. Return 0 on success, an errno value on failure. */ static int @@ -1344,10 +1399,10 @@ tzset_unlocked(void) ? lcl_is_set < 0 : 0 < lcl_is_set && strcmp(lcl_TZname, name) == 0) return; -#ifdef ALL_STATE +# ifdef ALL_STATE if (! sp) lclptr = sp = malloc(sizeof *lclptr); -#endif /* defined ALL_STATE */ +# endif if (sp) { if (zoneinit(sp, name) != 0) zoneinit(sp, ""); @@ -1358,6 +1413,9 @@ tzset_unlocked(void) lcl_is_set = lcl; } +#endif + +#if !USE_TIMEX_T void tzset(void) { @@ -1366,6 +1424,7 @@ tzset(void) tzset_unlocked(); unlock(); } +#endif static void gmtcheck(void) @@ -1384,7 +1443,7 @@ gmtcheck(void) unlock(); } -#if NETBSD_INSPIRED +#if NETBSD_INSPIRED && !USE_TIMEX_T timezone_t tzalloc(char const *name) @@ -1420,6 +1479,8 @@ tzfree(timezone_t sp) #endif +#if !USE_TIMEX_T || !defined TM_GMTOFF + /* ** The easy way to behave "as if no library function calls" localtime ** is to not call it, so we drop its guts into "localsub", which can be @@ -1474,14 +1535,14 @@ localsub(struct state const *sp, time_t const *timep, int_fast32_t setname, return NULL; /* "cannot happen" */ result = localsub(sp, &newt, setname, tmp); if (result) { -#if defined ckd_add && defined ckd_sub +# if defined ckd_add && defined ckd_sub if (t < sp->ats[0] ? ckd_sub(&result->tm_year, result->tm_year, years) : ckd_add(&result->tm_year, result->tm_year, years)) return NULL; -#else +# else register int_fast64_t newy; newy = result->tm_year; @@ -1491,7 +1552,7 @@ localsub(struct state const *sp, time_t const *timep, int_fast32_t setname, if (! (INT_MIN <= newy && newy <= INT_MAX)) return NULL; result->tm_year = newy; -#endif +# endif } return result; } @@ -1520,25 +1581,26 @@ localsub(struct state const *sp, time_t const *timep, int_fast32_t setname, result = timesub(&t, ttisp->tt_utoff, sp, tmp); if (result) { result->tm_isdst = ttisp->tt_isdst; -#ifdef TM_ZONE +# ifdef TM_ZONE result->TM_ZONE = (char *) &sp->chars[ttisp->tt_desigidx]; -#endif /* defined TM_ZONE */ +# endif if (setname) update_tzname_etc(sp, ttisp); } return result; } +#endif -#if NETBSD_INSPIRED +#if !USE_TIMEX_T +# if NETBSD_INSPIRED struct tm * localtime_rz(struct state *restrict sp, time_t const *restrict timep, struct tm *restrict tmp) { return localsub(sp, timep, 0, tmp); } - -#endif +# endif static struct tm * localtime_tzset(time_t const *timep, struct tm *tmp, bool setname) @@ -1558,9 +1620,9 @@ localtime_tzset(time_t const *timep, struct tm *tmp, bool setname) struct tm * localtime(const time_t *timep) { -#if !SUPPORT_C89 +# if !SUPPORT_C89 static struct tm tm; -#endif +# endif return localtime_tzset(timep, &tm, true); } @@ -1569,6 +1631,7 @@ localtime_r(const time_t *restrict timep, struct tm *restrict tmp) { return localtime_tzset(timep, tmp, false); } +#endif /* ** gmtsub is to gmtime as localsub is to localtime. @@ -1593,6 +1656,8 @@ gmtsub(ATTRIBUTE_MAYBE_UNUSED struct state const *sp, time_t const *timep, return result; } +#if !USE_TIMEX_T + /* * Re-entrant version of gmtime. */ @@ -1607,13 +1672,13 @@ gmtime_r(time_t const *restrict timep, struct tm *restrict tmp) struct tm * gmtime(const time_t *timep) { -#if !SUPPORT_C89 +# if !SUPPORT_C89 static struct tm tm; -#endif +# endif return gmtime_r(timep, &tm); } -#if STD_INSPIRED +# if STD_INSPIRED /* This function is obsolescent and may disappear in future releases. Callers can instead use localtime_rz with a fixed-offset zone. */ @@ -1623,12 +1688,13 @@ offtime(const time_t *timep, long offset) { gmtcheck(); -#if !SUPPORT_C89 +# if !SUPPORT_C89 static struct tm tm; -#endif +# endif return gmtsub(gmtptr, timep, offset, &tm); } +# endif #endif /* @@ -2189,6 +2255,8 @@ time1(struct tm *const tmp, return WRONG; } +#if !defined TM_GMTOFF || !USE_TIMEX_T + static time_t mktime_tzname(struct state *sp, struct tm *tmp, bool setname) { @@ -2200,16 +2268,9 @@ mktime_tzname(struct state *sp, struct tm *tmp, bool setname) } } -#if NETBSD_INSPIRED - -time_t -mktime_z(struct state *restrict sp, struct tm *restrict tmp) -{ - return mktime_tzname(sp, tmp, false); -} - -#endif - +# if USE_TIMEX_T +static +# endif time_t mktime(struct tm *tmp) { @@ -2225,7 +2286,17 @@ mktime(struct tm *tmp) return t; } -#if STD_INSPIRED +#endif + +#if NETBSD_INSPIRED && !USE_TIMEX_T +time_t +mktime_z(struct state *restrict sp, struct tm *restrict tmp) +{ + return mktime_tzname(sp, tmp, false); +} +#endif + +#if STD_INSPIRED && !USE_TIMEX_T /* This function is obsolescent and may disappear in future releases. Callers can instead use mktime. */ time_t @@ -2237,12 +2308,14 @@ timelocal(struct tm *tmp) } #endif -#ifndef EXTERN_TIMEOFF -# ifndef timeoff -# define timeoff my_timeoff /* Don't collide with OpenBSD 7.4 <time.h>. */ +#if defined TM_GMTOFF || !USE_TIMEX_T + +# ifndef EXTERN_TIMEOFF +# ifndef timeoff +# define timeoff my_timeoff /* Don't collide with OpenBSD 7.4 <time.h>. */ +# endif +# define EXTERN_TIMEOFF static # endif -# define EXTERN_TIMEOFF static -#endif /* This function is obsolescent and may disappear in future releases. Callers can instead use mktime_z with a fixed-offset zone. */ @@ -2254,7 +2327,9 @@ timeoff(struct tm *tmp, long offset) gmtcheck(); return time1(tmp, gmtsub, gmtptr, offset); } +#endif +#if !USE_TIMEX_T time_t timegm(struct tm *tmp) { @@ -2267,6 +2342,7 @@ timegm(struct tm *tmp) *tmp = tmcpy; return t; } +#endif static int_fast32_t leapcorr(struct state const *sp, time_t t) @@ -2287,15 +2363,16 @@ leapcorr(struct state const *sp, time_t t) ** XXX--is the below the right way to conditionalize?? */ -#if STD_INSPIRED +#if !USE_TIMEX_T +# if STD_INSPIRED /* NETBSD_INSPIRED_EXTERN functions are exported to callers if NETBSD_INSPIRED is defined, and are private otherwise. */ -# if NETBSD_INSPIRED -# define NETBSD_INSPIRED_EXTERN -# else -# define NETBSD_INSPIRED_EXTERN static -# endif +# if NETBSD_INSPIRED +# define NETBSD_INSPIRED_EXTERN +# else +# define NETBSD_INSPIRED_EXTERN static +# endif /* ** IEEE Std 1003.1 (POSIX) says that 536457599 @@ -2372,13 +2449,13 @@ posix2time(time_t t) return t; } -#endif /* STD_INSPIRED */ +# endif /* STD_INSPIRED */ -#if TZ_TIME_T +# if TZ_TIME_T -# if !USG_COMPAT -# define timezone 0 -# endif +# if !USG_COMPAT +# define timezone 0 +# endif /* Convert from the underlying system's time_t to the ersatz time_tz, which is called 'time_t' in this file. Typically, this merely @@ -2409,4 +2486,5 @@ time(time_t *p) return r; } +# endif #endif diff --git a/newstrftime.3 b/newstrftime.3 index 6fd01114..21aef9d0 100644 --- a/newstrftime.3 +++ b/newstrftime.3 @@ -247,15 +247,14 @@ of leap seconds. .TP %s is replaced by the number of seconds since the Epoch (see -.BR ctime (3)); -on the mostly-obsolescent platforms where this number can be out of range for -.BR time_t , -any out-of-range number is treated as if it were -.BR "((time_t) \*-1)" . +.BR ctime (3)). Although %s is reliable in this implementation, -it can have glitches on other platforms (notably platforms lacking -.IR tm_gmtoff ), -and POSIX also allows +it can have glitches on other platforms +(notably obsolescent platforms lacking +.I tm_gmtoff +or where +.B time_t +is no wider than int), and POSIX allows .B strftime to set .B errno diff --git a/private.h b/private.h index 730786dc..3a699f0f 100644 --- a/private.h +++ b/private.h @@ -626,6 +626,12 @@ static_assert(IINNTT_MIN < INT_MIN && INT_MAX < IINNTT_MAX); # define RESERVE_STD_EXT_IDS 0 #endif +#ifdef time_tz +# define defined_time_tz true +#else +# define defined_time_tz false +#endif + /* If standard C identifiers with external linkage (e.g., localtime) are reserved and are not already being renamed anyway, rename them as if compiling with '-Dtime_tz=time_t'. */ @@ -925,6 +931,55 @@ enum { SIGNED_PADDING_CHECK_NEEDED = true }; static_assert(! TYPE_SIGNED(time_t) || ! SIGNED_PADDING_CHECK_NEEDED || TIME_T_MAX >> (TYPE_BIT(time_t) - 2) == 1); +/* If true, the value returned by an idealized unlimited-range mktime + always fits into an integer type with bounds MIN and MAX. + If false, the value might not fit. + This macro is usable in #if if its arguments are. + Add or subtract 2**31 - 1 for the maximum UT offset allowed in a TZif file, + divide by the maximum number of non-leap seconds in a year, + divide again by two just to be safe, + and account for the tm_year origin (1900) and time_t origin (1970). */ +#define MKTIME_FITS_IN(min, max) \ + ((min) < 0 \ + && ((min) + 0x7fffffff) / 366 / 24 / 60 / 60 / 2 + 1970 - 1900 < INT_MIN \ + && INT_MAX < ((max) - 0x7fffffff) / 366 / 24 / 60 / 60 / 2 + 1970 - 1900) + +/* MKTIME_MIGHT_OVERFLOW is true if mktime can fail due to time_t overflow + or if it is not known whether mktime can fail, + and is false if mktime definitely cannot fail. + This macro is usable in #if, and so does not use TIME_T_MAX or sizeof. + If the builder has not configured this macro, guess based on what + known platforms do. When in doubt, guess true. */ +#ifndef MKTIME_MIGHT_OVERFLOW +# if defined __FreeBSD__ || defined __NetBSD__ || defined __OpenBSD__ +# include <sys/param.h> +# endif +# if ((/* The following heuristics assume native time_t. */ \ + defined_time_tz) \ + || ((/* Traditional time_t is 'long', so if 'long' is not wide enough \ + assume overflow unless we're on a known-safe host. */ \ + !MKTIME_FITS_IN(LONG_MIN, LONG_MAX)) \ + && (/* GNU C Library 2.29 (2019-02-01) and later has 64-bit time_t \ + if __TIMESIZE is 64. */ \ + !defined __TIMESIZE || __TIMESIZE < 64) \ + && (/* FreeBSD 12 r320347 (__FreeBSD_version 1200036; 2017-06-26), \ + and later has 64-bit time_t on all platforms but i386 which \ + is currently scheduled for end-of-life on 2028-11-30. */ \ + !defined __FreeBSD_version || __FreeBSD_version < 1200036 \ + || defined __i386) \ + && (/* NetBSD 6.0 (2012-10-17) and later has 64-bit time_t. */ \ + !defined __NetBSD_Version__ || __NetBSD_Version__ < 600000000) \ + && (/* OpenBSD 5.5 (2014-05-01) and later has 64-bit time_t. */ \ + !defined OpenBSD || OpenBSD < 201405))) +# define MKTIME_MIGHT_OVERFLOW true +# else +# define MKTIME_MIGHT_OVERFLOW false +# endif +#endif +/* Check that MKTIME_MIGHT_OVERFLOW is consistent with time_t's range. */ +static_assert(MKTIME_MIGHT_OVERFLOW + || MKTIME_FITS_IN(TIME_T_MIN, TIME_T_MAX)); + /* ** 302 / 1000 is log10(2.0) rounded up. ** Subtract one for the sign bit if the type is signed; @@ -957,7 +1012,7 @@ static_assert(! TYPE_SIGNED(time_t) || ! SIGNED_PADDING_CHECK_NEEDED /* strftime.c sometimes needs access to timeoff if it is not already public. tz_private_timeoff should be used only by localtime.c and strftime.c. */ -#if (!defined EXTERN_TIMEOFF \ +#if (!defined EXTERN_TIMEOFF && ! (defined USE_TIMEX_T && USE_TIMEX_T) \ && defined TM_GMTOFF && (200809 < _POSIX_VERSION || ! UNINIT_TRAP)) # ifndef timeoff # define timeoff tz_private_timeoff diff --git a/strftime.c b/strftime.c index a50ed90d..7be94de3 100644 --- a/strftime.c +++ b/strftime.c @@ -39,6 +39,12 @@ #include <locale.h> #include <stdio.h> +#if MKTIME_MIGHT_OVERFLOW +/* Do this after system includes as it redefines time_t, mktime, timeoff. */ +# define USE_TIMEX_T true +# include "localtime.c" +#endif + #ifndef DEPRECATE_TWO_DIGIT_YEARS # define DEPRECATE_TWO_DIGIT_YEARS false #endif @@ -328,16 +334,17 @@ label: tm.tm_mday = t->tm_mday; tm.tm_mon = t->tm_mon; tm.tm_year = t->tm_year; + + /* Get the time_t value for TM. + Native time_t, or its redefinition + by localtime.c above, is wide enough + so that this cannot overflow. */ #ifdef TM_GMTOFF mkt = timeoff(&tm, t->TM_GMTOFF); #else tm.tm_isdst = t->tm_isdst; mkt = mktime(&tm); #endif - /* If mktime fails, %s expands to the - value of (time_t) -1 as a failure - marker; this is better in practice - than strftime failing. */ if (TYPE_SIGNED(time_t)) { intmax_t n = mkt; sprintf(buf, "%"PRIdMAX, n); -- 2.47.0
participants (1)
-
Paul Eggert