* 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