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.
This leads to "Timezone right/UTC failed leap second check, ignoring" errors on musl based systems as musl does not support right/* timezones or returning leap second information in the tm_sec field of struct tm. This patch adds support for getting leap second information from the leap-seconds.list file included with tzdata and adds a new configuration directive leapsecdb to switch on the feature. --- conf.c | 14 ++++ conf.h | 1 + doc/chrony.conf.adoc | 19 ++++- reference.c | 162 +++++++++++++++++++++++++++++++++++++++++-- 4 files changed, 188 insertions(+), 8 deletions(-) diff --git a/conf.c b/conf.c index fa74459..3b1f70c 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 second database, usually /usr/share/zoneinfo/leap-seconds.list */ +static char *leapsec_db = 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_db); 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, "leapsecdb")) { + parse_string(p, &leapsec_db); } else if (!strcasecmp(command, "local")) { parse_local(p); } else if (!strcasecmp(command, "lock_all")) { @@ -2386,6 +2392,14 @@ CNF_GetLeapSecTimezone(void) /* ================================================== */ +char * +CNF_GetLeapSecDatabase(void) +{ + return leapsec_db; +} + +/* ================================================== */ + int CNF_GetSchedPriority(void) { diff --git a/conf.h b/conf.h index 58ebdeb..b6d9827 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_GetLeapSecDatabase(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..2c223fb 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 <<leapsecdb,*leapsecdb*>> 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 ---- +[[leapsecdb]]*leapsecdb* _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: ++ +---- +leapsecdb /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/reference.c b/reference.c index 97dfbe9..9d365eb 100644 --- a/reference.c +++ b/reference.c @@ -125,6 +125,9 @@ static SCH_TimeoutID leap_timeout_id; /* Name of a system timezone containing leap seconds occuring at midnight */ static char *leap_tzname; +/* File name of leap second database, usually /usr/share/zoneinfo/leap-seconds.list */ +static char *leap_db; + /* ================================================== */ static LOG_FileID logfileid; @@ -155,7 +158,20 @@ static int ref_adjustments; /* ================================================== */ +/* 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 get_tz_leap(time_t when, int *tai_offset); +static NTP_Leap get_db_leap(time_t when, int *tai_offset); +struct leapdb *read_leap_second_database(const char *file); static void update_leap_status(NTP_Leap leap, time_t now, int reset); /* ================================================== */ @@ -260,6 +276,18 @@ REF_Initialise(void) if (leap_mode == REF_LeapModeSystem && !LCL_CanSystemLeap()) leap_mode = REF_LeapModeStep; + leap_db = CNF_GetLeapSecDatabase(); + if (leap_db) { + /* Check that the leap database has good data for Jun 30 2012 and Dec 31 2012 */ + if (get_db_leap(1341014400, &tai_offset) == LEAP_InsertSecond && tai_offset == 34 && + get_db_leap(1356912000, &tai_offset) == LEAP_Normal && tai_offset == 35) { + LOG(LOGS_INFO, "Using leap second database %s", leap_db); + } else { + LOG(LOGS_WARN, "Leap second database %s failed check, ignoring", leap_db); + leap_db = NULL; + } + } + leap_tzname = CNF_GetLeapSecTimezone(); if (leap_tzname) { /* Check that the timezone has good data for Jun 30 2012 and Dec 31 2012 */ @@ -272,6 +300,11 @@ REF_Initialise(void) } } + if (leap_db && leap_tzname) { + LOG(LOGS_WARN, "Multiple leap second sources, ignoring leapsectz"); + leap_tzname = NULL; + } + CNF_GetMakeStep(&make_step_limit, &make_step_threshold); CNF_GetMaxChange(&max_offset_delay, &max_offset_ignore, &max_offset); CNF_GetMailOnChange(&do_mail_change, &mail_change_threshold, &mail_change_user); @@ -664,6 +697,124 @@ get_tz_leap(time_t when, int *tai_offset) /* ================================================== */ +static NTP_Leap +get_db_leap(time_t when, int *tai_offset) +{ + static time_t db_leap_check; + static NTP_Leap db_leap; + static int db_tai_offset; + static struct leapdb *db; + + *tai_offset = db_tai_offset; + + /* Do this check at most twice a day */ + when = when / (12 * 3600) * (12 * 3600); + if (db_leap_check == when) + return db_leap; + db_leap_check = when; + + struct leapdb *new_db = read_leap_second_database(leap_db); + if (new_db) { + free(db); + db = new_db; + } else + LOG(LOGS_ERR, "Failed to read leap second database %s", leap_db); + + if (!db) + 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 + 2208988800; + db_leap = LEAP_Normal; + + if (leap_when >= db->expiry) + LOG(LOGS_WARN, "Leap second database %s needs update", leap_db); + + /* 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! */ + db_tai_offset = lp_prev->tai_offset; + } else + db_tai_offset = lp->tai_offset; + + *tai_offset = db_tai_offset; + return db_leap; +} + +/* ================================================== */ + +struct leapdb * +read_leap_second_database(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 void leap_end_timeout(void *arg) { @@ -751,17 +902,18 @@ set_leap_timeout(time_t now) static void update_leap_status(NTP_Leap leap, time_t now, int reset) { - NTP_Leap tz_leap; + NTP_Leap tz_leap = LEAP_Normal; int leap_sec, tai_offset; leap_sec = 0; tai_offset = 0; - if (leap_tzname && now) { + if (leap_db && now) + tz_leap = get_db_leap(now, &tai_offset); + if (leap_tzname && now) tz_leap = get_tz_leap(now, &tai_offset); - if (leap == LEAP_Normal) - leap = tz_leap; - } + if (leap == LEAP_Normal) + leap = tz_leap; if (leap == LEAP_InsertSecond || leap == LEAP_DeleteSecond) { /* Check that leap second is allowed today */ -- 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.