From: Patrick Oppenlander <patrick.oppenlan...@gmail.com> 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 | 19 +++++- leapdb.c | 135 +++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 166 insertions(+), 3 deletions(-) diff --git a/conf.c b/conf.c index fa74459..06857a8 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")) { @@ -2386,6 +2392,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 abb8403..41646c4 100644 --- a/doc/chrony.conf.adoc +++ b/doc/chrony.conf.adoc @@ -672,9 +672,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 -<<leapsectz,*leapsectz*>> 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. +<<leapsectz,*leapsectz*>> or <<leapseclist,*leapseclist*>> 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 @@ -1261,6 +1262,18 @@ $ 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. +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 <<leapsectz,*leapsectz*>>. ++ +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 98bb2f5..c790c20 100644 --- a/leapdb.c +++ b/leapdb.c @@ -35,9 +35,24 @@ typedef NTP_Leap (*GetLeapFn)(time_t when, int *tai_offset); +/* Offset between leap-seconds.list epoch timestamps and Unix epoch. + leap-seconds.list epoch is 1 Jan 1900, 00:00:00 */ +static const long long LEAP_SECONDS_LIST_OFFSET = 2208988800; + /* Current leap second data source */ GetLeapFn get_leap; +/* Leap second database */ +struct leapdb { + long long updated; + long long expiry; + size_t len; + struct leap { + long long when; + int tai_offset; + } leap[]; +}; + /* ================================================== */ static NTP_Leap @@ -95,6 +110,110 @@ get_tz_leap(time_t when, int *tai_offset) /* ================================================== */ +static struct leapdb * +read_leap_seconds_list(const char *file) +{ + size_t ll = 0; + size_t len = 32; /* 28 entries to 1 Jan 2017 */ + struct leapdb *db = calloc(1, sizeof *db + sizeof *db->leap * len); + FILE *f = fopen(file, "r"); + char *l = NULL; + + if (!db || !f) + goto error; + + while (getline(&l, &ll, f) > 0) { + if (*l == '#') { + /* update time */ + if (l[1] == '$' && sscanf(l + 2, "%lld", &db->updated) != 1) + goto error; + /* expiration time */ + if (l[1] == '@' && sscanf(l + 2, "%lld", &db->expiry) != 1) + goto error; + /* comment or a special comment we don't care about */ + continue; + } + + if (db->len >= len) { + len += len / 2; + void *p = realloc(db, sizeof *db + sizeof *db->leap * len); + if (!p) + goto error; + db = p; + } + + /* leap entry */ + struct leap *lp = db->leap + db->len; + if (sscanf(l, "%lld %d", &lp->when, &lp->tai_offset) != 2) + goto error; + ++db->len; + } + + /* make sure the database looks sensible */ + if (!feof(f) || !db->updated || !db->expiry || !db->len) + goto error; + + goto out; + +error: + free(db); + db = 0; +out: + free(l); + if (f) + fclose(f); + return db; +} + +/* ================================================== */ + +static NTP_Leap +get_db_leap(time_t when, int *tai_offset) +{ + NTP_Leap db_leap = LEAP_Normal; + struct leapdb *db = read_leap_seconds_list(CNF_GetLeapSecList()); + + if (!db) { + LOG(LOGS_ERR, "Failed to read leap second list %s", CNF_GetLeapSecList()); + return db_leap; + } + + /* 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 */ + long long leap_when = when + LEAP_SECONDS_LIST_OFFSET; + db_leap = LEAP_Normal; + + if (leap_when >= db->expiry) + LOG(LOGS_WARN, "Leap second list %s needs update", CNF_GetLeapSecList()); + + /* find leap entry */ + struct leap *lp = db->leap; + for (int i = db->len - 1; i >= 0; --i) { + lp = db->leap + i; + if (leap_when >= lp->when) + break; + } + + if (leap_when == lp->when) { + struct leap *lp_prev = lp > db->leap ? lp - 1 : db->leap; + if (lp->tai_offset > lp_prev->tai_offset) { + db_leap = LEAP_InsertSecond; + } else if (lp->tai_offset < lp_prev->tai_offset) + db_leap = LEAP_DeleteSecond; + /* tai offset hasn't changed yet! */ + *tai_offset = lp_prev->tai_offset; + } else + *tai_offset = lp->tai_offset; + + free(db); + + return db_leap; +} + +/* ================================================== */ + static int check_leap_source(GetLeapFn fn) { @@ -119,6 +238,22 @@ LDB_Initialise(void) leap_tzname = NULL; } + const char *leap_list = CNF_GetLeapSecList(); + if (leap_list && !check_leap_source(get_db_leap)) { + LOG(LOGS_WARN, "Leap second list %s failed check, ignoring", leap_list); + leap_list = NULL; + } + + if (leap_list && leap_tzname) { + LOG(LOGS_WARN, "Multiple leap second sources, ignoring leapsectz"); + leap_tzname = NULL; + } + + if (leap_list) { + LOG(LOGS_INFO, "Using leap second list %s", leap_list); + get_leap = get_db_leap; + } + if (leap_tzname) { LOG(LOGS_INFO, "Using %s timezone to obtain leap second data", leap_tzname); get_leap = get_tz_leap; -- 2.43.0 -- To unsubscribe email chrony-dev-requ...@chrony.tuxfamily.org with "unsubscribe" in the subject. For help email chrony-dev-requ...@chrony.tuxfamily.org with "help" in the subject. Trouble? Email listmas...@chrony.tuxfamily.org.