Paul Eggert via tz <[email protected]> writes:
> I didn't know this this code was used in freebsd-src/contrib/tzcode. I
> now see that there are some minor differences between the FreeBSD
> copy; would it make sense to merge these into tzcode, perhaps with
> some "#ifdef __FreeBSD__"s thrown in? I suspect that would save time
> overall in the long run.
I wouldn't call these differences minor, the diff in localtime.c alone
is 450+ lines and includes changes which you've rejected in the past.
I've attached a cleaned-up diff. Almost everything in it is already
#ifdef'ed.
DES
--
Dag-Erling Smørgrav - [email protected]
--- tz/localtime.c 2025-09-23 13:02:24.859014000 +0200
+++ contrib/tzcode/localtime.c 2025-09-23 13:07:07.985192000 +0200
@@ -13,11 +13,24 @@
/*LINTLIBRARY*/
#define LOCALTIME_IMPLEMENTATION
+#ifdef __FreeBSD__
+#include <pthread.h>
+#endif /* __FreeBSD__ */
+#ifdef DETECT_TZ_CHANGES
+# ifndef DETECT_TZ_CHANGES_INTERVAL
+# define DETECT_TZ_CHANGES_INTERVAL 61
+# endif
+int __tz_change_interval = DETECT_TZ_CHANGES_INTERVAL;
+# include <sys/stat.h>
+#endif /* DETECT_TZ_CHANGES */
#include "private.h"
#include "tzdir.h"
#include "tzfile.h"
#include <fcntl.h>
+#ifdef __FreeBSD__
+#include "libc_private.h"
+#endif /* __FreeBSD__ */
#if HAVE_SYS_STAT_H
# include <sys/stat.h>
@@ -29,6 +42,10 @@
#if defined THREAD_SAFE && THREAD_SAFE
# include <pthread.h>
+#ifdef __FreeBSD__
+# define pthread_mutex_lock(l) (__isthreaded ? pthread_mutex_lock(l) : 0)
+# define pthread_mutex_unlock(l) (__isthreaded ? pthread_mutex_unlock(l) : 0)
+#endif /* __FreeBSD__ */
static pthread_mutex_t locallock = PTHREAD_MUTEX_INITIALIZER;
static int lock(void) { return pthread_mutex_lock(&locallock); }
static void unlock(void) { pthread_mutex_unlock(&locallock); }
@@ -250,6 +267,9 @@
int_fast32_t r_time; /* transition time of rule */
};
+#ifdef __FreeBSD__
+static void tzset_unlocked_name(char const *);
+#endif /* __FreeBSD__ */
static struct tm *gmtsub(struct state const *, time_t const *, int_fast32_t,
struct tm *);
static bool increment_overflow(int *, int);
@@ -279,6 +299,18 @@
static char lcl_TZname[TZ_STRLEN_MAX + 1];
static int lcl_is_set;
#endif
+#ifdef __FreeBSD__
+static pthread_once_t gmt_once = PTHREAD_ONCE_INIT;
+static pthread_once_t gmtime_once = PTHREAD_ONCE_INIT;
+static pthread_key_t gmtime_key;
+static int gmtime_key_error;
+static pthread_once_t offtime_once = PTHREAD_ONCE_INIT;
+static pthread_key_t offtime_key;
+static int offtime_key_error;
+static pthread_once_t localtime_once = PTHREAD_ONCE_INIT;
+static pthread_key_t localtime_key;
+static int localtime_key_error;
+#endif /* __FreeBSD__ */
/*
** Section 4.12.3 of X3.159-1989 requires that
@@ -476,6 +508,42 @@
#endif
+#ifdef DETECT_TZ_CHANGES
+/*
+ * Check whether either the time zone name or the file it refers to has
+ * changed since the last time we checked.
+ * Returns: -1 on error
+ * 0 if the time zone has not changed
+ * 1 if the time zone has changed
+ */
+static int
+tzfile_changed(const char *name, int fd)
+{
+ static char old_name[PATH_MAX];
+ static struct stat old_sb;
+ struct stat sb;
+
+ if (fstat(fd, &sb) != 0)
+ return -1;
+
+ if (strcmp(name, old_name) != 0) {
+ strlcpy(old_name, name, sizeof(old_name));
+ old_sb = sb;
+ return 1;
+ }
+
+ if (sb.st_dev != old_sb.st_dev ||
+ sb.st_ino != old_sb.st_ino ||
+ sb.st_ctime != old_sb.st_ctime ||
+ sb.st_mtime != old_sb.st_mtime) {
+ old_sb = sb;
+ return 1;
+ }
+
+ return 0;
+}
+#endif /* DETECT_TZ_CHANGES */
+
/* Input buffer for data read from a compiled tz file. */
union input_buffer {
/* The first part of the buffer, interpreted as a header. */
@@ -487,8 +555,10 @@
+ 4 * TZ_MAX_TIMES];
};
+#ifndef __FreeBSD__
/* TZDIR with a trailing '/' rather than a trailing '\0'. */
static char const tzdirslash[sizeof TZDIR] = TZDIR "/";
+#endif /* !__FreeBSD__ */
/* Local storage needed for 'tzloadbody'. */
union local_storage {
@@ -501,6 +571,7 @@
struct state st;
} u;
+#ifndef __FreeBSD__
/* The name of the file to be opened. Ideally this would have no
size limits, to support arbitrarily long Zone names.
Limiting Zone names to 1024 bytes should suffice for practical use.
@@ -508,6 +579,7 @@
file_analysis as that struct is allocated anyway, as the other
union member. */
char fullname[max(sizeof(struct file_analysis), sizeof tzdirslash + 1024)];
+#endif /* !__FreeBSD__ */
};
/* These tzload flags can be ORed together, and fit into 'char'. */
@@ -525,7 +597,13 @@
register int fid;
register int stored;
register ssize_t nread;
+#ifdef __FreeBSD__
+ struct stat sb;
+ const char *relname;
+ int dd, serrno;
+#else /* !__FreeBSD__ */
register bool doaccess;
+#endif /* !__FreeBSD__ */
register union input_buffer *up = &lsp->u.u;
register int tzheadsize = sizeof(struct tzhead);
@@ -540,6 +618,7 @@
if (name[0] == ':')
++name;
+#ifndef __FreeBSD__
#ifdef SUPPRESS_TZDIR
/* Do not prepend TZDIR. This is intended for specialized
applications only, due to its security implications. */
@@ -589,9 +668,60 @@
}
fid = open(name, (O_RDONLY | O_BINARY | O_CLOEXEC | O_CLOFORK
| O_IGNORE_CTTY | O_NOCTTY));
+#else /* __FreeBSD__ */
+ if ((tzloadflags & TZLOAD_FROMENV) && strcmp(name, TZDEFAULT) == 0)
+ tzloadflags &= ~TZLOAD_FROMENV;
+ relname = name;
+ if (strncmp(relname, TZDIR "/", strlen(TZDIR) + 1) == 0)
+ relname += strlen(TZDIR) + 1;
+ dd = open(TZDIR, O_DIRECTORY | O_RDONLY);
+ if ((tzloadflags & TZLOAD_FROMENV) && issetugid()) {
+ if (dd < 0)
+ return errno;
+ if (fstatat(dd, name, &sb, AT_RESOLVE_BENEATH) < 0) {
+ fid = -1;
+ } else if (!S_ISREG(sb.st_mode)) {
+ fid = -1;
+ errno = EINVAL;
+ } else {
+ fid = openat(dd, relname, O_RDONLY | O_BINARY, AT_RESOLVE_BENEATH);
+ }
+ } else {
+ if (dd < 0) {
+ relname = name;
+ dd = AT_FDCWD;
+ }
+ fid = openat(dd, relname, O_RDONLY | O_BINARY, 0);
+ }
+ if (dd != AT_FDCWD && dd >= 0) {
+ serrno = errno;
+ close(dd);
+ errno = serrno;
+ }
+#endif /* __FreeBSD__ */
if (fid < 0)
return errno;
+#ifdef DETECT_TZ_CHANGES
+ if (tzloadflags) {
+ /*
+ * Detect if the timezone file has changed. Check tzloadflags
+ * to ignore TZDEFRULES; the tzfile_changed() function can only
+ * keep state for a single file.
+ */
+ switch (tzfile_changed(name, fid)) {
+ case -1:
+ serrno = errno;
+ close(fid);
+ return serrno;
+ case 0:
+ close(fid);
+ return 0;
+ case 1:
+ break;
+ }
+ }
+#endif /* DETECT_TZ_CHANGES */
nread = read(fid, up->buf, sizeof up->buf);
if (nread < tzheadsize) {
int err = nread < 0 ? errno : EINVAL;
@@ -1439,6 +1569,29 @@
tzparse("UTC0", sp, NULL);
}
+#ifdef DETECT_TZ_CHANGES
+/*
+ * Check if the time zone data we have is still fresh.
+ */
+static int
+tzdata_is_fresh(void)
+{
+ static time_t last_checked;
+ struct timespec now;
+
+ if (clock_gettime(CLOCK_MONOTONIC, &now) < 0)
+ return 0;
+
+ if ((now.tv_sec - last_checked >= __tz_change_interval) ||
+ (last_checked > now.tv_sec)) {
+ last_checked = now.tv_sec;
+ return 1;
+ }
+
+ return 0;
+}
+#endif /* DETECT_TZ_CHANGES */
+
#if !USE_TIMEX_T || !defined TM_GMTOFF
/* Initialize *SP to a value appropriate for the TZ setting NAME.
@@ -1473,11 +1626,21 @@
tzset_unlocked(void)
{
char const *name = getenv("TZ");
+#ifdef __FreeBSD__
+ tzset_unlocked_name(name);
+}
+static void
+tzset_unlocked_name(char const *name)
+{
+#endif
struct state *sp = lclptr;
int lcl = name ? strlen(name) < sizeof lcl_TZname : -1;
if (lcl < 0
? lcl_is_set < 0
: 0 < lcl_is_set && strcmp(lcl_TZname, name) == 0)
+#ifdef DETECT_TZ_CHANGES
+ if (tzdata_is_fresh() == 0)
+#endif /* DETECT_TZ_CHANGES */
return;
# ifdef ALL_STATE
if (! sp)
@@ -1512,6 +1675,19 @@
}
#endif
+#ifdef __FreeBSD__
+void
+freebsd13_tzsetwall(void)
+{
+ if (lock() != 0)
+ return;
+ tzset_unlocked_name(NULL);
+ unlock();
+}
+__sym_compat(tzsetwall, freebsd13_tzsetwall, FBSD_1.0);
+__warn_references(tzsetwall,
+ "warning: tzsetwall() is deprecated, use tzset() instead.");
+#endif /* __FreeBSD__ */
static void
gmtcheck(void)
{
@@ -1528,6 +1704,9 @@
}
unlock();
}
+#ifdef __FreeBSD__
+#define gmtcheck() _once(&gmt_once, gmtcheck)
+#endif
#if NETBSD_INSPIRED && !USE_TIMEX_T
@@ -1696,20 +1875,49 @@
errno = err;
return NULL;
}
+#ifndef DETECT_TZ_CHANGES
if (setname || !lcl_is_set)
+#endif /* DETECT_TZ_CHANGES */
tzset_unlocked();
tmp = localsub(lclptr, timep, setname, tmp);
unlock();
return tmp;
}
+#ifdef __FreeBSD__
+static void
+localtime_key_init(void)
+{
+ localtime_key_error = pthread_key_create(&localtime_key, free);
+}
+#endif /* __FreeBSD__ */
struct tm *
localtime(const time_t *timep)
{
# if !SUPPORT_C89
static struct tm tm;
# endif
- return localtime_tzset(timep, &tm, true);
+#ifdef __FreeBSD__
+ struct tm *p_tm = &tm;
+
+ if (__isthreaded != 0) {
+ pthread_once(&localtime_once, localtime_key_init);
+ if (localtime_key_error != 0) {
+ errno = localtime_key_error;
+ return (NULL);
+ }
+ if ((p_tm = pthread_getspecific(localtime_key)) == NULL) {
+ if ((p_tm = malloc(sizeof(*p_tm))) == NULL) {
+ return (NULL);
+ }
+ if (pthread_setspecific(localtime_key, p_tm) != 0) {
+ free(p_tm);
+ return (NULL);
+ }
+ }
+ }
+#endif /* __FreeBSD__ */
+ return localtime_tzset(timep, p_tm, true);
}
struct tm *
@@ -1755,13 +1963,40 @@
return gmtsub(gmtptr, timep, 0, tmp);
}
+#ifdef __FreeBSD__
+static void
+gmtime_key_init(void)
+{
+ gmtime_key_error = pthread_key_create(&gmtime_key, free);
+}
+#endif /* __FreeBSD__ */
struct tm *
gmtime(const time_t *timep)
{
# if !SUPPORT_C89
static struct tm tm;
# endif
- return gmtime_r(timep, &tm);
+#ifdef __FreeBSD__
+ struct tm *p_tm = &tm;
+
+ if (__isthreaded != 0) {
+ pthread_once(&gmtime_once, gmtime_key_init);
+ if (gmtime_key_error != 0) {
+ errno = gmtime_key_error;
+ return (NULL);
+ }
+ if ((p_tm = pthread_getspecific(gmtime_key)) == NULL) {
+ if ((p_tm = malloc(sizeof(*p_tm))) == NULL) {
+ return (NULL);
+ }
+ if (pthread_setspecific(gmtime_key, p_tm) != 0) {
+ free(p_tm);
+ return (NULL);
+ }
+ }
+ }
+#endif /* __FreeBSD__ */
+ return gmtime_r(timep, p_tm);
}
# if STD_INSPIRED
@@ -1770,14 +2005,46 @@
Callers can instead use localtime_rz with a fixed-offset zone. */
struct tm *
-offtime(const time_t *timep, long offset)
+offtime_r(time_t const *restrict timep, long offset, struct tm *restrict tmp)
{
gmtcheck();
+ return gmtsub(gmtptr, timep, offset, tmp);
+}
+#ifdef __FreeBSD__
+static void
+offtime_key_init(void)
+{
+ offtime_key_error = pthread_key_create(&offtime_key, free);
+}
+#endif /* __FreeBSD__ */
+struct tm *
+offtime(const time_t *timep, long offset)
+{
# if !SUPPORT_C89
static struct tm tm;
# endif
- return gmtsub(gmtptr, timep, offset, &tm);
+#ifdef __FreeBSD__
+ struct tm *p_tm = &tm;
+
+ if (__isthreaded != 0) {
+ pthread_once(&offtime_once, offtime_key_init);
+ if (offtime_key_error != 0) {
+ errno = offtime_key_error;
+ return (NULL);
+ }
+ if ((p_tm = pthread_getspecific(offtime_key)) == NULL) {
+ if ((p_tm = malloc(sizeof(*p_tm))) == NULL) {
+ return (NULL);
+ }
+ if (pthread_setspecific(offtime_key, p_tm) != 0) {
+ free(p_tm);
+ return (NULL);
+ }
+ }
+ }
+#endif
+ return offtime_r(timep, offset, p_tm);
}
# endif
@@ -2474,7 +2741,9 @@
errno = err;
return -1;
}
+#ifndef DETECT_TZ_CHANGES
if (!lcl_is_set)
+#endif /* DETECT_TZ_CHANGES */
tzset_unlocked();
if (lclptr)
t = time2posix_z(lclptr, t);
@@ -2519,7 +2788,9 @@
errno = err;
return -1;
}
+#ifndef DETECT_TZ_CHANGES
if (!lcl_is_set)
+#endif /* DETECT_TZ_CHANGES */
tzset_unlocked();
if (lclptr)
t = posix2time_z(lclptr, t);