From 53823b9f1c076b3b26d0ad031eb95a51e6842abd Mon Sep 17 00:00:00 2001 From: Patrick Oppenlander Date: Thu, 8 Feb 2024 14:36:28 +1100 Subject: [PATCH] leapdb: support leap-seconds.list as second source The existing implementation of getting leap second information from a timezone in get_tz_leap() relies on non-portable C library behaviour. Specifically, mktime is not required to return '60' in the tm_sec field when a leap second is inserted leading to "Timezone right/UTC failed leap second check, ignoring" errors on musl based systems. This patch adds support for getting leap second information from the leap-seconds.list file included with tzdata and adds a new configuration directive leapseclist to switch on the feature. --- conf.c | 14 +++++ conf.h | 1 + doc/chrony.conf.adoc | 20 +++++-- leapdb.c | 126 +++++++++++++++++++++++++++++++++++++++++-- refclock.c | 4 +- 5 files changed, 155 insertions(+), 10 deletions(-) diff --git a/conf.c b/conf.c index 73fe774..6eae11c 100644 --- a/conf.c +++ b/conf.c @@ -249,6 +249,9 @@ static REF_LeapMode leapsec_mode = REF_LeapModeSystem; /* Name of a system timezone containing leap seconds occuring at midnight */ static char *leapsec_tz = NULL; +/* File name of leap seconds list, usually /usr/share/zoneinfo/leap-seconds.list */ +static char *leapsec_list = NULL; + /* Name of the user to which will be dropped root privileges. */ static char *user; @@ -471,6 +474,7 @@ CNF_Finalise(void) Free(hwclock_file); Free(keys_file); Free(leapsec_tz); + Free(leapsec_list); Free(logdir); Free(bind_ntp_iface); Free(bind_acq_iface); @@ -620,6 +624,8 @@ CNF_ParseLine(const char *filename, int number, char *line) parse_leapsecmode(p); } else if (!strcasecmp(command, "leapsectz")) { parse_string(p, &leapsec_tz); + } else if (!strcasecmp(command, "leapseclist")) { + parse_string(p, &leapsec_list); } else if (!strcasecmp(command, "local")) { parse_local(p); } else if (!strcasecmp(command, "lock_all")) { @@ -2389,6 +2395,14 @@ CNF_GetLeapSecTimezone(void) /* ================================================== */ +char * +CNF_GetLeapSecList(void) +{ + return leapsec_list; +} + +/* ================================================== */ + int CNF_GetSchedPriority(void) { diff --git a/conf.h b/conf.h index 58ebdeb..4c0a787 100644 --- a/conf.h +++ b/conf.h @@ -91,6 +91,7 @@ extern char *CNF_GetNtpSigndSocket(void); extern char *CNF_GetPidFile(void); extern REF_LeapMode CNF_GetLeapSecMode(void); extern char *CNF_GetLeapSecTimezone(void); +extern char *CNF_GetLeapSecList(void); /* Value returned in ppm, as read from file */ extern double CNF_GetMaxUpdateSkew(void); diff --git a/doc/chrony.conf.adoc b/doc/chrony.conf.adoc index eeaa501..bd296bc 100644 --- a/doc/chrony.conf.adoc +++ b/doc/chrony.conf.adoc @@ -680,9 +680,10 @@ trusted and required source. *tai*::: This option indicates that the reference clock keeps time in TAI instead of UTC and that *chronyd* should correct its offset by the current TAI-UTC offset. The -<> directive must be used with this option and the -database must be kept up to date in order for this correction to work as -expected. This option does not make sense with PPS refclocks. +<> or <> directive must be +used with this option and the database must be kept up to date in order for +this correction to work as expected. This option does not make sense with PPS +refclocks. *local*::: This option specifies that the reference clock is an unsynchronised clock which is more stable than the system clock (e.g. TCXO, OCXO, or atomic clock) and @@ -1269,6 +1270,19 @@ $ TZ=right/UTC date -d 'Dec 31 2008 23:59:60' Wed Dec 31 23:59:60 UTC 2008 ---- +[[leapseclist]]*leapseclist* _file_:: +This directive specifies the path to a file containing a list of leap seconds +and TAI-UTC offsets in NIST/IERS format. It is recommended to use +the file _leap-seconds.list_ usually included with the system timezone +database. The behaviour of this directive is otherwise equivalent to +<>. ++ +An example of this directive is: ++ +---- +leapseclist /usr/share/zoneinfo/leap-seconds.list +---- + [[makestep]]*makestep* _threshold_ _limit_:: Normally *chronyd* will cause the system to gradually correct any time offset, by slowing down or speeding up the clock as required. In certain situations, diff --git a/leapdb.c b/leapdb.c index aa49b3c..e748e00 100644 --- a/leapdb.c +++ b/leapdb.c @@ -3,6 +3,7 @@ ********************************************************************** * Copyright (C) Miroslav Lichvar 2009-2018, 2020, 2022 + * Copyright (C) Patrick Oppenlander 2023, 2024 * * This program is free software; you can redistribute it and/or modify * it under the terms of version 2 of the GNU General Public License as @@ -30,11 +31,20 @@ #include "conf.h" #include "leapdb.h" #include "logging.h" +#include "util.h" /* ================================================== */ -/* Name of a system timezone containing leap seconds occuring at midnight */ -static char *leap_tzname; +/* Source of leap second data */ +enum { + SRC_NONE, + SRC_TIMEZONE, + SRC_LIST, +} leap_src; + +/* Offset between leap-seconds.list timestamp epoch and Unix epoch. + leap-seconds.list epoch is 1 Jan 1900, 00:00:00 */ +#define LEAP_SEC_LIST_OFFSET 2208988800 /* ================================================== */ @@ -59,7 +69,7 @@ get_tz_leap(time_t when, int *tai_offset) return tz_leap; strcpy(tz_orig, tz_env); } - setenv("TZ", leap_tzname, 1); + setenv("TZ", CNF_GetLeapSecTimezone(), 1); tzset(); /* Get the TAI-UTC offset, which started at the epoch at 10 seconds */ @@ -93,6 +103,91 @@ get_tz_leap(time_t when, int *tai_offset) /* ================================================== */ +static NTP_Leap +get_list_leap(time_t when, int *tai_offset) +{ + FILE *f; + char line[1024]; + NTP_Leap ret_leap = LEAP_Normal; + int ret_tai_offset = 0, prev_lsl_tai_offset = 10; + int64_t lsl_updated = 0, lsl_expiry = 0; + const char *leap_sec_list = CNF_GetLeapSecList(); + + if (!(f = UTI_OpenFile(NULL, leap_sec_list, NULL, 'r', 0))) { + LOG(LOGS_ERR, "Failed to open leap seconds list %s", leap_sec_list); + goto out; + } + + /* Leap second happens at midnight */ + when = (when / (24 * 3600) + 1) * (24 * 3600); + + /* leap-seconds.list timestamps are relative to 1 Jan 1900, 00:00:00 */ + when += LEAP_SEC_LIST_OFFSET; + + while (fgets(line, sizeof line, f) > 0) { + int64_t lsl_when; + int lsl_tai_offset; + char *p; + + /* Ignore blank lines */ + for (p = line; *p && isspace(*p); ++p) + ; + if (!*p) + continue; + + if (*line == '#') { + /* Update time line starts with #$ */ + if (line[1] == '$' && sscanf(line + 2, "%"SCNd64, &lsl_updated) != 1) + goto error; + /* Expiration time line starts with #@ */ + if (line[1] == '@' && sscanf(line + 2, "%"SCNd64, &lsl_expiry) != 1) + goto error; + /* Comment or a special comment we don't care about */ + continue; + } + + /* Leap entry */ + if (sscanf(line, "%"SCNd64" %d", &lsl_when, &lsl_tai_offset) != 2) + goto error; + + if (when == lsl_when) { + if (lsl_tai_offset > prev_lsl_tai_offset) + ret_leap = LEAP_InsertSecond; + else if (lsl_tai_offset < prev_lsl_tai_offset) + ret_leap = LEAP_DeleteSecond; + /* When is rounded to the end of the day, so offset hasn't changed yet! */ + ret_tai_offset = prev_lsl_tai_offset; + } else if (when > lsl_when) { + ret_tai_offset = lsl_tai_offset; + } + + prev_lsl_tai_offset = lsl_tai_offset; + } + + /* Make sure the file looks sensible */ + if (!feof(f) || !lsl_updated || !lsl_expiry) + goto error; + + if (when >= lsl_expiry) + LOG(LOGS_WARN, "Leap second list %s needs update", leap_sec_list); + + goto out; + +error: + if (f) + fclose(f); + LOG(LOGS_ERR, "Failed to parse leap seconds list %s", leap_sec_list); + return LEAP_Normal; + +out: + if (f) + fclose(f); + *tai_offset = ret_tai_offset; + return ret_leap; +} + +/* ================================================== */ + static int check_leap_source(NTP_Leap (*src)(time_t when, int *tai_offset)) { @@ -111,14 +206,27 @@ check_leap_source(NTP_Leap (*src)(time_t when, int *tai_offset)) void LDB_Initialise(void) { + const char *leap_tzname, *leap_sec_list; + leap_tzname = CNF_GetLeapSecTimezone(); if (leap_tzname && !check_leap_source(get_tz_leap)) { LOG(LOGS_WARN, "Timezone %s failed leap second check, ignoring", leap_tzname); leap_tzname = NULL; } - if (leap_tzname) + leap_sec_list = CNF_GetLeapSecList(); + if (leap_sec_list && !check_leap_source(get_list_leap)) { + LOG(LOGS_WARN, "Leap second list %s failed check, ignoring", leap_sec_list); + leap_sec_list = NULL; + } + + if (leap_sec_list) { + LOG(LOGS_INFO, "Using leap second list %s", leap_sec_list); + leap_src = SRC_LIST; + } else if (leap_tzname) { LOG(LOGS_INFO, "Using %s timezone to obtain leap second data", leap_tzname); + leap_src = SRC_TIMEZONE; + } } /* ================================================== */ @@ -139,8 +247,16 @@ LDB_GetLeap(time_t when, int *tai_offset) ldb_leap = LEAP_Normal; ldb_tai_offset = 0; - if (leap_tzname) + switch (leap_src) { + case SRC_NONE: + break; + case SRC_TIMEZONE: ldb_leap = get_tz_leap(when, &ldb_tai_offset); + break; + case SRC_LIST: + ldb_leap = get_list_leap(when, &ldb_tai_offset); + break; + } out: *tai_offset = ldb_tai_offset; diff --git a/refclock.c b/refclock.c index 84f7439..44ba6d5 100644 --- a/refclock.c +++ b/refclock.c @@ -166,8 +166,8 @@ RCL_AddRefclock(RefclockParameters *params) if (!inst->driver->init && !inst->driver->poll) LOG_FATAL("refclock driver %s is not compiled in", params->driver_name); - if (params->tai && !CNF_GetLeapSecTimezone()) - LOG_FATAL("refclock tai option requires leapsectz"); + if (params->tai && !CNF_GetLeapSecList() && !CNF_GetLeapSecTimezone()) + LOG_FATAL("refclock tai option requires leapseclist or leapsectz"); inst->data = NULL; inst->driver_parameter = Strdup(params->driver_parameter);