diff options
Diffstat (limited to 'contrib/tzcode/strftime.c')
-rw-r--r-- | contrib/tzcode/strftime.c | 83 |
1 files changed, 67 insertions, 16 deletions
diff --git a/contrib/tzcode/strftime.c b/contrib/tzcode/strftime.c index 755d341fa6d9..487a5234cbc5 100644 --- a/contrib/tzcode/strftime.c +++ b/contrib/tzcode/strftime.c @@ -39,8 +39,63 @@ #include <locale.h> #include <stdio.h> +/* 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 1 +# else +# define MKTIME_MIGHT_OVERFLOW 0 +# 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)); + +#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 +# define DEPRECATE_TWO_DIGIT_YEARS 0 #endif struct lc_time_T { @@ -135,10 +190,6 @@ strftime(char *restrict s, size_t maxsize, char const *restrict format, tzset(); p = _fmt(format, t, s, s + maxsize, &warn); - if (!p) { - errno = EOVERFLOW; - return 0; - } if (DEPRECATE_TWO_DIGIT_YEARS && warn != IN_NONE && getenv(YEAR_2000_NAME)) { fprintf(stderr, "\n"); @@ -170,7 +221,12 @@ _fmt(const char *format, const struct tm *t, char *pt, if (*format == '%') { label: switch (*++format) { - case '\0': + default: + /* Output unknown conversion specifiers as-is, + to aid debugging. This includes '%' at + format end. This conforms to C23 section + 7.29.3.5 paragraph 6, which says behavior + is undefined here. */ --format; break; case 'A': @@ -327,16 +383,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); @@ -590,12 +647,6 @@ label: warnp); continue; case '%': - /* - ** X311J/88-090 (4.12.3.5): if conversion char is - ** undefined, behavior is undefined. Print out the - ** character itself as printf(3) also does. - */ - default: break; } } |