* Makefile, NEWS: Mention it. * localtime.c (THREAD_TM_MULTI): Default to 0. The remaining changes affect only the THREAD_TM_MULTI case. (enum tm_multi): New enum. (N_TM_MULTI): New constant. (tm_multi_key, tm_multi_key_err): New static vars. (tm_multi): New static function. (localtime, gmtime, offtime): Use it. --- Makefile | 8 ++++++++ NEWS | 9 +++++++++ localtime.c | 56 ++++++++++++++++++++++++++++++++++++++++++++++++++--- 3 files changed, 70 insertions(+), 3 deletions(-) diff --git a/Makefile b/Makefile index 8939869a..b172542a 100644 --- a/Makefile +++ b/Makefile @@ -316,6 +316,14 @@ LDLIBS= # This can improve paralellism and thus save real time # if many threads call tzcode functions simultaneously. # It also costs CPU time and thus energy. +# -DTHREAD_TM_MULTI to have gmtime, localtime, and offtime +# return different struct tm * addresses in different threads. +# This supports unportable programs that call +# gmtime/localtime/offtime when they should call +# gmtime_r/localtime_r/offtime_r to avoid races. +# Because the corresponding storage is freed on thread exit, +# this option is incompatible with POSIX.1-2024 and earlier. +# It also costs CPU time and memory. # -Dtime_tz=\"T\" to use T as the time_t type, rather than the system time_t # This is intended for internal use only; it mangles external names. # -DTZ_CHANGE_INTERVAL=N if functions depending on TZ should check diff --git a/NEWS b/NEWS index 5bfb7bb4..46bf48ed 100644 --- a/NEWS +++ b/NEWS @@ -80,6 +80,15 @@ Unreleased, experimental changes rarely changing and many threads call tzcode simultaneously. It costs more CPU time and energy. + The new CFLAGS option -TTHREAD_TM_MULTI causes localtime to return + a pointer to thread-specific memory, as FreeBSD does, instead of + to the same memory in all threads. This supports unportable + programs that incorrectly use localtime instead of localtime_r. + This option affects gmtime and offtime similarly to localtime. + Because the corresponding storage is freed on thread exit, this + option is incompatible with POSIX.1-2024 and earlier. It also + costs CPU time and memory. + tzcode now uses mempcpy if available, guessing its availability. Compile with -DHAVE_MEMPCPY=1 or 0 to override the guess. diff --git a/localtime.c b/localtime.c index 1191f810..709b954c 100644 --- a/localtime.c +++ b/localtime.c @@ -52,6 +52,10 @@ struct stat { char st_ctime, st_dev, st_ino; } # define THREAD_RWLOCK 0 #endif +#ifndef THREAD_TM_MULTI +# define THREAD_TM_MULTI 0 +#endif + #if THREAD_SAFE # include <pthread.h> @@ -171,6 +175,52 @@ once(once_t *once_control, void (*init_routine)(void)) #endif } +enum tm_multi { LOCALTIME_TM_MULTI, GMTIME_TM_MULTI, OFFTIME_TM_MULTI }; + +#if THREAD_SAFE && THREAD_TM_MULTI + +enum { N_TM_MULTI = OFFTIME_TM_MULTI + 1 }; +static pthread_key_t tm_multi_key; +static int tm_multi_key_err; + +static void +tm_multi_key_init(void) +{ + tm_multi_key_err = pthread_key_create(&tm_multi_key, free); +} + +#endif + +/* Return TMP, or a thread-specific struct tm * selected by WHICH. */ +static struct tm * +tm_multi(struct tm *tmp, ATTRIBUTE_MAYBE_UNUSED enum tm_multi which) +{ +#if THREAD_SAFE && THREAD_TM_MULTI + /* It is OK to check is_threaded() separately here; even if it + returns a different value in other places in the caller, + this function's behavior is still valid. */ + if (is_threaded()) { + /* Try to get a thread-specific struct tm *. + Fall back on TMP if this fails. */ + static pthread_once_t tm_multi_once = PTHREAD_ONCE_INIT; + pthread_once(&tm_multi_once, tm_multi_key_init); + if (!tm_multi_key_err) { + struct tm *p = pthread_getspecific(tm_multi_key); + if (!p) { + p = malloc(N_TM_MULTI * sizeof *p); + if (p && pthread_setspecific(tm_multi_key, p) != 0) { + free(p); + p = NULL; + } + } + if (p) + return &p[which]; + } + } +#endif + return tmp; +} + /* Unless intptr_t is missing, pacify gcc -Wcast-qual on char const * exprs. Use this carefully, as the casts disable type checking. This is a macro so that it can be used in static initializers. */ @@ -2156,7 +2206,7 @@ localtime(const time_t *timep) # if !SUPPORT_C89 static struct tm tm; # endif - return localtime_tzset(timep, &tm, true); + return localtime_tzset(timep, tm_multi(&tm, LOCALTIME_TM_MULTI), true); } struct tm * @@ -2208,7 +2258,7 @@ gmtime(const time_t *timep) # if !SUPPORT_C89 static struct tm tm; # endif - return gmtime_r(timep, &tm); + return gmtime_r(timep, tm_multi(&tm, GMTIME_TM_MULTI)); } # if STD_INSPIRED @@ -2229,7 +2279,7 @@ offtime(time_t const *timep, long offset) # if !SUPPORT_C89 static struct tm tm; # endif - return offtime_r(timep, offset, &tm); + return offtime_r(timep, offset, tm_multi(&tm, OFFTIME_TM_MULTI)); } # endif -- 2.48.1