[tz] Timezone change detection
Edward Tomasz Napierała
trasz at freebsd.org
Fri Sep 3 16:53:40 UTC 2021
Hello. I'm working on upstreaming a patch, which originally came
from NetApp, to modify localtime.c to automatically detect changes
to the time zone. The use case is to make the applications handle
moving between timezones, or changing the default system timezone.
The idea is pretty simple: just stat(2) the timezone file inode
to make sure it still refers to the same file it previously did,
and make sure to do this at most once per minute.
The patch (with the FreeBSD-specific build glue stripped) follows;
previous discussion can be found at https://reviews.freebsd.org/D30183.
The patch was written for FreeBSD version of localtime.c, which differs
a bit from upstream, but as there seems to be an effort underway
to sync FreeBSD with upstream, I believe it also makes sense to ask for
comments here before commiting it to FreeBSD. Any feedback is welcome.
Thanks :-)
diff --git a/contrib/tzcode/stdtime/localtime.c b/contrib/tzcode/stdtime/localtime.c
index e221c1fa396..926b24470e1 100644
--- a/contrib/tzcode/stdtime/localtime.c
+++ b/contrib/tzcode/stdtime/localtime.c
@@ -354,6 +354,45 @@ settzname(void)
}
}
+#ifdef DETECT_TZ_CHANGES
+/*
+ * Determine if there's a change in the timezone since the last time we checked.
+ * Returns: -1 on error
+ * 0 if the timezone has not changed
+ * 1 if the timezone has changed
+ */
+static int
+change_in_tz(const char *name)
+{
+ static char old_name[PATH_MAX];
+ static struct stat old_sb;
+ struct stat sb;
+ int error;
+
+ error = stat(name, &sb);
+ if (error != 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;
+}
+#else /* !DETECT_TZ_CHANGES */
+#define change_in_tz(X) 0
+#endif /* !DETECT_TZ_CHANGES */
+
static int
differ_by_repeat(const time_t t1, const time_t t0)
{
@@ -379,6 +418,7 @@ register const int doextend;
int stored;
int nread;
int res;
+ int ret;
union {
struct tzhead tzhead;
char buf[2 * sizeof(struct tzhead) +
@@ -427,6 +467,22 @@ register const int doextend;
(void) strcat(fullname, name);
name = fullname;
}
+ if (doextend == TRUE) {
+ /*
+ * Detect if the timezone file has changed. Check
+ * 'doextend' to ignore TZDEFRULES; the change_in_tz()
+ * function can only keep state for a single file.
+ */
+ ret = change_in_tz(name);
+ if (ret <= 0) {
+ /*
+ * Returns -1 if there was an error,
+ * and 0 if the timezone had not changed.
+ */
+ free(fullname);
+ return ret;
+ }
+ }
if ((fid = _open(name, OPEN_MODE)) == -1) {
free(fullname);
return -1;
@@ -1209,12 +1265,43 @@ gmtload(struct state *const sp)
(void) tzparse(gmt, sp, TRUE);
}
+#ifdef DETECT_TZ_CHANGES
+static int
+recheck_tzdata()
+{
+ static time_t last_checked;
+ struct timespec now;
+ time_t current_time;
+ int error;
+
+ /*
+ * We want to recheck the timezone file every 61 sec.
+ */
+ error = clock_gettime(CLOCK_MONOTONIC, &now);
+ if (error <= 0) {
+ /* XXX: Can we somehow report this? */
+ return 0;
+ }
+
+ current_time = now.tv_sec;
+ if ((current_time - last_checked > 61) ||
+ (last_checked > current_time)) {
+ last_checked = current_time;
+ return 1;
+ }
+
+ return 0;
+}
+#else /* !DETECT_TZ_CHANGES */
+#define recheck_tzdata() 0
+#endif /* !DETECT_TZ_CHANGES */
+
static void
tzsetwall_basic(int rdlocked)
{
if (!rdlocked)
_RWLOCK_RDLOCK(&lcl_rwlock);
- if (lcl_is_set < 0) {
+ if (lcl_is_set < 0 && recheck_tzdata() == 0) {
if (!rdlocked)
_RWLOCK_UNLOCK(&lcl_rwlock);
return;
More information about the tz
mailing list