This aligns nstrftime better with draft POSIX 202x strftime. * lib/nstrftime.c: Include errno.h. (width_add, __strftime_internal): Set errno on failure, and preserve it on success. Check for mktime_z failure. * modules/nstrftime (Depends-on): Add errno. * modules/nstrftime-tests (Depends-on): Add atoll, intprops. * tests/test-nstrftime.c: Include intprops.h, limits.h. (errno_test): New test function. (main): Call it. --- ChangeLog | 13 +++++++ lib/nstrftime.c | 14 +++++++- lib/strftime.h | 7 +++- modules/nstrftime | 1 + modules/nstrftime-tests | 2 ++ tests/test-nstrftime.c | 79 +++++++++++++++++++++++++++++++++++++++++ 6 files changed, 114 insertions(+), 2 deletions(-)
diff --git a/ChangeLog b/ChangeLog index 86b512991..eb80aba80 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,16 @@ +2020-08-15 Paul Eggert <egg...@cs.ucla.edu> + + nstrftime: be more predictable about errno + This aligns nstrftime better with draft POSIX 202x strftime. + * lib/nstrftime.c: Include errno.h. + (width_add, __strftime_internal): Set errno on failure, + and preserve it on success. Check for mktime_z failure. + * modules/nstrftime (Depends-on): Add errno. + * modules/nstrftime-tests (Depends-on): Add atoll, intprops. + * tests/test-nstrftime.c: Include intprops.h, limits.h. + (errno_test): New test function. + (main): Call it. + 2020-08-15 Bruno Haible <br...@clisp.org> canonicalize: Fix a problem of the autoconf test on MSVC/clang. diff --git a/lib/nstrftime.c b/lib/nstrftime.c index 35b65bbbd..28bc42fd6 100644 --- a/lib/nstrftime.c +++ b/lib/nstrftime.c @@ -33,6 +33,7 @@ #endif #include <ctype.h> +#include <errno.h> #include <time.h> #if HAVE_TZNAME && !HAVE_DECL_TZNAME @@ -162,7 +163,10 @@ extern char *tzname[]; size_t _w = pad == L_('-') || width < 0 ? 0 : width; \ size_t _incr = _n < _w ? _w : _n; \ if (_incr >= maxsize - i) \ - return 0; \ + { \ + errno = ERANGE; \ + return 0; \ + } \ if (p) \ { \ if (_n < _w) \ @@ -447,6 +451,7 @@ __strftime_internal (STREAM_OR_CHAR_T *s, STRFTIME_ARG (size_t maxsize) size_t maxsize = (size_t) -1; #endif + int saved_errno = errno; int hour12 = tp->tm_hour; #ifdef _NL_CURRENT /* We cannot make the following values variables since we must delay @@ -1186,7 +1191,13 @@ __strftime_internal (STREAM_OR_CHAR_T *s, STRFTIME_ARG (size_t maxsize) time_t t; ltm = *tp; + ltm.tm_yday = -1; t = mktime_z (tz, <m); + if (ltm.tm_yday < 0) + { + errno = EOVERFLOW; + return 0; + } /* Generate string value for T using time_t arithmetic; this works even if sizeof (long) < sizeof (time_t). */ @@ -1484,5 +1495,6 @@ __strftime_internal (STREAM_OR_CHAR_T *s, STRFTIME_ARG (size_t maxsize) *p = L_('\0'); #endif + errno = saved_errno; return i; } diff --git a/lib/strftime.h b/lib/strftime.h index e85016315..fe0c4195a 100644 --- a/lib/strftime.h +++ b/lib/strftime.h @@ -24,7 +24,12 @@ extern "C" { /* Just like strftime, but with two more arguments: POSIX requires that strftime use the local timezone information. Use the timezone __TZ instead. Use __NS as the number of - nanoseconds in the %N directive. */ + nanoseconds in the %N directive. + + On error, set errno and return 0. Otherwise, return the number of + bytes generated (not counting the trailing NUL), preserving errno + if the number is 0. This errno behavior is in draft POSIX 202x + plus some requested changes to POSIX. */ size_t nstrftime (char *restrict, size_t, char const *, struct tm const *, timezone_t __tz, int __ns); diff --git a/modules/nstrftime b/modules/nstrftime index 7ff82896b..9db0c7521 100644 --- a/modules/nstrftime +++ b/modules/nstrftime @@ -9,6 +9,7 @@ m4/nstrftime.m4 Depends-on: attribute +errno extensions intprops stdbool diff --git a/modules/nstrftime-tests b/modules/nstrftime-tests index 708b510d2..4089771e0 100644 --- a/modules/nstrftime-tests +++ b/modules/nstrftime-tests @@ -3,6 +3,8 @@ tests/test-nstrftime.c tests/macros.h Depends-on: +atoll +intprops strerror configure.ac: diff --git a/tests/test-nstrftime.c b/tests/test-nstrftime.c index cb9e2d6ad..63864fc83 100644 --- a/tests/test-nstrftime.c +++ b/tests/test-nstrftime.c @@ -20,7 +20,10 @@ #include "strftime.h" +#include "intprops.h" + #include <errno.h> +#include <limits.h> #include <stdio.h> #include <string.h> #include <time.h> @@ -247,6 +250,81 @@ quarter_test (void) return result; } +static int +errno_test (void) +{ + int fail = 0; + struct tm tm = { .tm_year = 2020 - 1900, .tm_mday = 1 }; + char buf[INT_BUFSIZE_BOUND (time_t)]; + size_t n; + int bigyear = LLONG_MAX - 1900 < INT_MAX ? LLONG_MAX - 1900 : INT_MAX; + + errno = 0; + n = nstrftime (buf, 0, "%m", &tm, 0, 0); + if (! (n == 0 && errno == ERANGE)) + { + fail = 1; + printf ("nstrftime failed to set errno = ERANGE\n"); + } + + errno = 0; + n = nstrftime (buf, sizeof buf, "", &tm, 0, 0); + if (! (n == 0 && errno == 0)) + { + fail = 1; + printf ("nstrftime failed to leave errno alone\n"); + } + + + tm.tm_year = bigyear; + errno = 0; + n = nstrftime (buf, sizeof buf, "%s", &tm, 0, 0); + if (n == 0) + { + if (errno != EOVERFLOW) + { + fail = 1; + printf ("nstrftime failed to set errno = EOVERFLOW\n"); + } + + if (mktime_z (0, &tm) != (time_t) -1) + { + fail = 1; + printf ("nstrftime %%s failed but mktime_z worked for tm_year=%d\n", + bigyear); + } + } + else + { + long long int text_seconds = atoll (buf); + if (text_seconds <= (LLONG_MAX - 1 < TYPE_MAXIMUM (time_t) + ? LLONG_MAX - 1 : TYPE_MAXIMUM (time_t))) + { + time_t bigtime = text_seconds; + struct tm *tmp = gmtime (&bigtime); + if (!tmp) + { + fail = 1; + printf ("gmtime failed on nstrftime result\n"); + } + else + { + char buf1[sizeof buf]; + size_t n1 = nstrftime (buf1, sizeof buf1, "%s", tmp, 0, 0); + buf1[n1] = '\0'; + if (! STREQ (buf, buf1)) + { + fail = 1; + printf ("nstrftime %%s first returned '%s', then '%s'\n", + buf, buf1); + } + } + } + } + + return fail; +} + int main (void) { @@ -254,6 +332,7 @@ main (void) fail |= posixtm_test (); fail |= tzalloc_test (); fail |= quarter_test (); + fail |= errno_test (); return fail; } -- 2.17.1