[PROPOSED 1/2] Remove dependency of asctime on strftime
Also, remove calls to strlen and strcpy. * asctime.c (ASCTIME_FMT, ASCTIME_FMT_B): Remove; the strings are now merely literals in asctime_r. (SNPRINTF, SNPRINTFBUF): New macros. (asctime_r): Do not call strftime %Y, as this is dicey when tm_year is not a value that could be yielded by localtime. Instead, use snprintf, or sprintf on ancient hosts lacking snprintf. Also, rely on snprintf/sprintf's return value instead of calling strlen, as it's safe to assume C89 b6 now. If snprintf is available, output to the result buffer directly instead of to the stack and then copying. This saves one call to strftime, two calls to strlen, and one call to strcpy. * private.h (HAVE_SNPRINTF): Move here ... * zdump.c: ... from here. --- asctime.c | 96 +++++++++++++++++++++++++++++++------------------------ private.h | 4 +++ zdump.c | 4 --- 3 files changed, 58 insertions(+), 46 deletions(-) diff --git a/asctime.c b/asctime.c index f75ec868..7bc19fd1 100644 --- a/asctime.c +++ b/asctime.c @@ -7,6 +7,7 @@ /* ** Avoid the temptation to punt entirely to strftime; +** strftime can behave badly when tm components are out of range, and ** the output of strftime is supposed to be locale specific ** whereas the output of asctime is supposed to be constant. */ @@ -16,27 +17,6 @@ #include "private.h" #include <stdio.h> -/* -** All years associated with 32-bit time_t values are exactly four digits long; -** some years associated with 64-bit time_t values are not. -** Vintage programs are coded for years that are always four digits long -** and may assume that the newline always lands in the same place. -** For years that are less than four digits, we pad the output with -** leading zeroes to get the newline in the traditional place. -** The -4 ensures that we get four characters of output even if -** we call a strftime variant that produces fewer characters for some years. -** This conforms to recent ISO C and POSIX standards, which say behavior -** is undefined when the year is less than 1000 or greater than 9999. -*/ -static char const ASCTIME_FMT[] = "%s %s%3d %.2d:%.2d:%.2d %-4s\n"; -/* -** For years that are more than four digits we put extra spaces before the year -** so that code trying to overwrite the newline won't end up overwriting -** a digit within a year and truncating the year (operating on the assumption -** that no output is better than wrong output). -*/ -static char const ASCTIME_FMT_B[] = "%s %s%3d %.2d:%.2d:%.2d %s\n"; - enum { STD_ASCTIME_BUF_SIZE = 26 }; /* ** Big enough for something such as @@ -84,8 +64,20 @@ asctime_r(struct tm const *restrict timeptr, char *restrict buf) }; register const char * wn; register const char * mn; - char year[INT_STRLEN_MAXIMUM(int) + 2]; - char result[sizeof buf_asctime]; + int year, mday, hour, min, sec; + size_t bufsize = ((buf == buf_ctime + || (!SUPPORT_C89 && buf == buf_asctime)) + ? sizeof buf_asctime : STD_ASCTIME_BUF_SIZE); +#if HAVE_SNPRINTF + char *result = buf; +# define SNPRINTF snprintf +# define SNPRINTFBUF(buf, bufsize) buf, bufsize +#else + char stackbuf[sizeof buf_asctime]; + char *result = stackbuf; +# define SNPRINTF sprintf +# define SNPRINTFBUF(buf, bufsize) buf +#endif if (timeptr == NULL) { errno = EINVAL; @@ -97,26 +89,46 @@ asctime_r(struct tm const *restrict timeptr, char *restrict buf) if (timeptr->tm_mon < 0 || timeptr->tm_mon >= MONSPERYEAR) mn = "???"; else mn = mon_name[timeptr->tm_mon]; - /* - ** Use strftime's %Y to generate the year, to avoid overflow problems - ** when computing timeptr->tm_year + TM_YEAR_BASE. - ** Assume that strftime is unaffected by other out-of-range members - ** (e.g., timeptr->tm_mday) when processing "%Y". - */ - strftime(year, sizeof year, "%Y", timeptr); - /* - ** We avoid using snprintf since it's not available on all systems. - */ - sprintf(result, - ((strlen(year) <= 4) ? ASCTIME_FMT : ASCTIME_FMT_B), - wn, mn, - timeptr->tm_mday, timeptr->tm_hour, - timeptr->tm_min, timeptr->tm_sec, - year); - if (strlen(result) < STD_ASCTIME_BUF_SIZE - || buf == buf_ctime || buf == buf_asctime) + + year = timeptr->tm_year; + mday = timeptr->tm_mday; + hour = timeptr->tm_hour; + min = timeptr->tm_min; + sec = timeptr->tm_sec; + + /* Vintage programs are coded for years that are always four bytes long + and may assume that the newline always lands in the same place. + For years that are less than four bytes, pad the output with + leading zeroes to get the newline in the traditional place. + For years longer than four bytes, put extra spaces before the year + so that vintage code trying to overwrite the newline + won't overwrite a digit within a year and truncate the year, + using the principle that no output is better than wrong output. + This conforms to ISO C and POSIX standards, which say behavior + is undefined when the year is less than 1000 or greater than 9999. + + Also, avoid overflow when formatting tm_year + TM_YEAR_BASE. */ + + if ((year <= INT_MAX - TM_YEAR_BASE + ? SNPRINTF (SNPRINTFBUF(result, bufsize), + ((-999 - TM_YEAR_BASE <= year + && year <= 9999 - TM_YEAR_BASE) + ? "%s %s%3d %.2d:%.2d:%.2d %04d\n" + : "%s %s%3d %.2d:%.2d:%.2d %d\n"), + wn, mn, mday, hour, min, sec, + year + TM_YEAR_BASE) + : SNPRINTF (SNPRINTFBUF(result, bufsize), + "%s %s%3d %.2d:%.2d:%.2d %d%d\n", + wn, mn, mday, hour, min, sec, + year / 10 + TM_YEAR_BASE / 10, + year % 10)) + < bufsize) { +#if HAVE_SNPRINTF + return buf; +#else return strcpy(buf, result); - else { +#endif + } else { errno = EOVERFLOW; return NULL; } diff --git a/private.h b/private.h index da6dc790..6088d885 100644 --- a/private.h +++ b/private.h @@ -263,6 +263,10 @@ # endif #endif +#ifndef HAVE_SNPRINTF +# define HAVE_SNPRINTF (!PORT_TO_C89 || 199901 <= __STDC_VERSION__) +#endif + #ifndef HAVE_STRFTIME_L # if _POSIX_VERSION < 200809 # define HAVE_STRFTIME_L 0 diff --git a/zdump.c b/zdump.c index 2c6725e5..c988f6b3 100644 --- a/zdump.c +++ b/zdump.c @@ -14,10 +14,6 @@ #include "private.h" #include <stdio.h> -#ifndef HAVE_SNPRINTF -# define HAVE_SNPRINTF (!PORT_TO_C89 || 199901 <= __STDC_VERSION__) -#endif - #ifndef HAVE_LOCALTIME_R # define HAVE_LOCALTIME_R 1 #endif -- 2.43.0
* asctime.c (asctime_r): Don’t assume strcpy preserves errno. Although POSIX.1-2024 requires it, earlier versions of POSIX did not. --- asctime.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/asctime.c b/asctime.c index 7bc19fd1..51ce5a50 100644 --- a/asctime.c +++ b/asctime.c @@ -80,8 +80,11 @@ asctime_r(struct tm const *restrict timeptr, char *restrict buf) #endif if (timeptr == NULL) { + strcpy(buf, "??? ??? ?? ??:??:?? ????\n"); + /* Set errno now, since strcpy might change it in + POSIX.1-2017 and earlier. */ errno = EINVAL; - return strcpy(buf, "??? ??? ?? ??:??:?? ????\n"); + return buf; } if (timeptr->tm_wday < 0 || timeptr->tm_wday >= DAYSPERWEEK) wn = "???"; -- 2.43.0
participants (1)
-
Paul Eggert