* 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

Reply via email to