diff --git a/017d6f8f5630071e2a532f58f9513757d94ac7d5.patch b/017d6f8f5630071e2a532f58f9513757d94ac7d5.patch new file mode 100644 index 0000000..0d70531 --- /dev/null +++ b/017d6f8f5630071e2a532f58f9513757d94ac7d5.patch @@ -0,0 +1,419 @@ +From 017d6f8f5630071e2a532f58f9513757d94ac7d5 Mon Sep 17 00:00:00 2001 +From: Patrick Oppenlander +Date: Thu, 8 Feb 2024 14:36:25 +1100 +Subject: [PATCH] reference: move leap second source into leapdb + +Separate out source of leap second data into a new module in preparation +for supporting more sources such as leap-seconds.list. +--- + Makefile.in | 2 +- + leapdb.c | 147 ++++++++++++++++++++++++++++++++++++++++++++++++++++ + leapdb.h | 37 +++++++++++++ + main.c | 3 ++ + reference.c | 99 +++-------------------------------- + 5 files changed, 194 insertions(+), 94 deletions(-) + create mode 100644 leapdb.c + create mode 100644 leapdb.h + +diff --git a/Makefile.in b/Makefile.in +index 101e0c69..318109bb 100644 +--- a/Makefile.in ++++ b/Makefile.in +@@ -37,7 +37,7 @@ GETDATE_CFLAGS = @GETDATE_CFLAGS@ + + EXTRA_OBJS = @EXTRA_OBJS@ + +-OBJS = array.o cmdparse.o conf.o local.o logging.o main.o memory.o quantiles.o \ ++OBJS = array.o cmdparse.o conf.o leapdb.o local.o logging.o main.o memory.o quantiles.o \ + reference.o regress.o rtc.o samplefilt.o sched.o socket.o sources.o sourcestats.o \ + stubs.o smooth.o sys.o sys_null.o tempcomp.o util.o $(EXTRA_OBJS) + +diff --git a/leapdb.c b/leapdb.c +new file mode 100644 +index 00000000..676a0d5d +--- /dev/null ++++ b/leapdb.c +@@ -0,0 +1,147 @@ ++/* ++ chronyd/chronyc - Programs for keeping computer clocks accurate. ++ ++ ********************************************************************** ++ * Copyright (C) Miroslav Lichvar 2009-2018, 2020, 2022 ++ * ++ * 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 ++ * published by the Free Software Foundation. ++ * ++ * This program is distributed in the hope that it will be useful, but ++ * WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ++ * General Public License for more details. ++ * ++ * You should have received a copy of the GNU General Public License along ++ * with this program; if not, write to the Free Software Foundation, Inc., ++ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. ++ * ++ ********************************************************************** ++ ++ ======================================================================= ++ ++ This module provides leap second information. */ ++ ++#include "config.h" ++ ++#include "sysincl.h" ++ ++#include "conf.h" ++#include "leapdb.h" ++#include "logging.h" ++ ++/* ================================================== */ ++ ++/* Name of a system timezone containing leap seconds occuring at midnight */ ++static char *leap_tzname; ++ ++/* ================================================== */ ++ ++static NTP_Leap ++get_tz_leap(time_t when, int *tai_offset) ++{ ++ static time_t last_tz_leap_check; ++ static NTP_Leap tz_leap; ++ static int tz_tai_offset; ++ ++ struct tm stm, *tm; ++ time_t t; ++ char *tz_env, tz_orig[128]; ++ ++ *tai_offset = tz_tai_offset; ++ ++ /* Do this check at most twice a day */ ++ when = when / (12 * 3600) * (12 * 3600); ++ if (last_tz_leap_check == when) ++ return tz_leap; ++ ++ last_tz_leap_check = when; ++ tz_leap = LEAP_Normal; ++ tz_tai_offset = 0; ++ ++ tm = gmtime(&when); ++ if (!tm) ++ return tz_leap; ++ ++ stm = *tm; ++ ++ /* Temporarily switch to the timezone containing leap seconds */ ++ tz_env = getenv("TZ"); ++ if (tz_env) { ++ if (strlen(tz_env) >= sizeof (tz_orig)) ++ return tz_leap; ++ strcpy(tz_orig, tz_env); ++ } ++ setenv("TZ", leap_tzname, 1); ++ tzset(); ++ ++ /* Get the TAI-UTC offset, which started at the epoch at 10 seconds */ ++ t = mktime(&stm); ++ if (t != -1) ++ tz_tai_offset = t - when + 10; ++ ++ /* Set the time to 23:59:60 and see how it overflows in mktime() */ ++ stm.tm_sec = 60; ++ stm.tm_min = 59; ++ stm.tm_hour = 23; ++ ++ t = mktime(&stm); ++ ++ if (tz_env) ++ setenv("TZ", tz_orig, 1); ++ else ++ unsetenv("TZ"); ++ tzset(); ++ ++ if (t == -1) ++ return tz_leap; ++ ++ if (stm.tm_sec == 60) ++ tz_leap = LEAP_InsertSecond; ++ else if (stm.tm_sec == 1) ++ tz_leap = LEAP_DeleteSecond; ++ ++ *tai_offset = tz_tai_offset; ++ ++ return tz_leap; ++} ++ ++/* ================================================== */ ++ ++void ++LDB_Initialise(void) ++{ ++ int tai_offset; ++ ++ leap_tzname = CNF_GetLeapSecTimezone(); ++ if (leap_tzname) { ++ /* Check that the timezone has good data for Jun 30 2012 and Dec 31 2012 */ ++ if (get_tz_leap(1341014400, &tai_offset) == LEAP_InsertSecond && tai_offset == 34 && ++ get_tz_leap(1356912000, &tai_offset) == LEAP_Normal && tai_offset == 35) { ++ LOG(LOGS_INFO, "Using %s timezone to obtain leap second data", leap_tzname); ++ } else { ++ LOG(LOGS_WARN, "Timezone %s failed leap second check, ignoring", leap_tzname); ++ leap_tzname = NULL; ++ } ++ } ++} ++ ++/* ================================================== */ ++ ++NTP_Leap ++LDB_GetLeap(time_t when, int *tai_offset) ++{ ++ *tai_offset = 0; ++ if (leap_tzname) ++ return get_tz_leap(when, tai_offset); ++ return LEAP_Normal; ++} ++ ++/* ================================================== */ ++ ++void ++LDB_Finalise(void) ++{ ++ /* Nothing to do */ ++} +diff --git a/leapdb.h b/leapdb.h +new file mode 100644 +index 00000000..eab24141 +--- /dev/null ++++ b/leapdb.h +@@ -0,0 +1,37 @@ ++/* ++ chronyd/chronyc - Programs for keeping computer clocks accurate. ++ ++ ********************************************************************** ++ * Copyright (C) Patrick Oppenlander 2023 ++ * ++ * 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 ++ * published by the Free Software Foundation. ++ * ++ * This program is distributed in the hope that it will be useful, but ++ * WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ++ * General Public License for more details. ++ * ++ * You should have received a copy of the GNU General Public License along ++ * with this program; if not, write to the Free Software Foundation, Inc., ++ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. ++ * ++ ********************************************************************** ++ ++ ======================================================================= ++ ++ This module provides leap second information. ++ ++ */ ++ ++#ifndef GOT_LEAPDB_H ++#define GOT_LEAPDB_H ++ ++#include "ntp.h" ++ ++extern void LDB_Initialise(void); ++extern NTP_Leap LDB_GetLeap(time_t when, int *tai_offset); ++extern void LDB_Finalise(void); ++ ++#endif /* GOT_LEAPDB_H */ +diff --git a/main.c b/main.c +index 21d0fe7f..cb240640 100644 +--- a/main.c ++++ b/main.c +@@ -32,6 +32,7 @@ + + #include "main.h" + #include "sched.h" ++#include "leapdb.h" + #include "local.h" + #include "sys.h" + #include "ntp_io.h" +@@ -134,6 +135,7 @@ MAI_CleanupAndExit(void) + RCL_Finalise(); + SRC_Finalise(); + REF_Finalise(); ++ LDB_Finalise(); + RTC_Finalise(); + SYS_Finalise(); + +@@ -655,6 +657,7 @@ int main + if (!geteuid()) + LOG(LOGS_WARN, "Running with root privileges"); + ++ LDB_Initialise(); + REF_Initialise(); + SST_Initialise(); + NSR_Initialise(); +diff --git a/reference.c b/reference.c +index 97dfbe98..1ac6cb93 100644 +--- a/reference.c ++++ b/reference.c +@@ -33,6 +33,7 @@ + #include "reference.h" + #include "util.h" + #include "conf.h" ++#include "leapdb.h" + #include "logging.h" + #include "local.h" + #include "sched.h" +@@ -122,9 +123,6 @@ static int leap_in_progress; + /* Timer for the leap second handler */ + static SCH_TimeoutID leap_timeout_id; + +-/* Name of a system timezone containing leap seconds occuring at midnight */ +-static char *leap_tzname; +- + /* ================================================== */ + + static LOG_FileID logfileid; +@@ -155,7 +153,6 @@ static int ref_adjustments; + + /* ================================================== */ + +-static NTP_Leap get_tz_leap(time_t when, int *tai_offset); + static void update_leap_status(NTP_Leap leap, time_t now, int reset); + + /* ================================================== */ +@@ -195,7 +192,6 @@ REF_Initialise(void) + FILE *in; + double file_freq_ppm, file_skew_ppm; + double our_frequency_ppm; +- int tai_offset; + + mode = REF_ModeNormal; + are_we_synchronised = 0; +@@ -260,18 +256,6 @@ REF_Initialise(void) + if (leap_mode == REF_LeapModeSystem && !LCL_CanSystemLeap()) + leap_mode = REF_LeapModeStep; + +- leap_tzname = CNF_GetLeapSecTimezone(); +- if (leap_tzname) { +- /* Check that the timezone has good data for Jun 30 2012 and Dec 31 2012 */ +- if (get_tz_leap(1341014400, &tai_offset) == LEAP_InsertSecond && tai_offset == 34 && +- get_tz_leap(1356912000, &tai_offset) == LEAP_Normal && tai_offset == 35) { +- LOG(LOGS_INFO, "Using %s timezone to obtain leap second data", leap_tzname); +- } else { +- LOG(LOGS_WARN, "Timezone %s failed leap second check, ignoring", leap_tzname); +- 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); +@@ -593,77 +577,6 @@ is_leap_second_day(time_t when) + + /* ================================================== */ + +-static NTP_Leap +-get_tz_leap(time_t when, int *tai_offset) +-{ +- static time_t last_tz_leap_check; +- static NTP_Leap tz_leap; +- static int tz_tai_offset; +- +- struct tm stm, *tm; +- time_t t; +- char *tz_env, tz_orig[128]; +- +- *tai_offset = tz_tai_offset; +- +- /* Do this check at most twice a day */ +- when = when / (12 * 3600) * (12 * 3600); +- if (last_tz_leap_check == when) +- return tz_leap; +- +- last_tz_leap_check = when; +- tz_leap = LEAP_Normal; +- tz_tai_offset = 0; +- +- tm = gmtime(&when); +- if (!tm) +- return tz_leap; +- +- stm = *tm; +- +- /* Temporarily switch to the timezone containing leap seconds */ +- tz_env = getenv("TZ"); +- if (tz_env) { +- if (strlen(tz_env) >= sizeof (tz_orig)) +- return tz_leap; +- strcpy(tz_orig, tz_env); +- } +- setenv("TZ", leap_tzname, 1); +- tzset(); +- +- /* Get the TAI-UTC offset, which started at the epoch at 10 seconds */ +- t = mktime(&stm); +- if (t != -1) +- tz_tai_offset = t - when + 10; +- +- /* Set the time to 23:59:60 and see how it overflows in mktime() */ +- stm.tm_sec = 60; +- stm.tm_min = 59; +- stm.tm_hour = 23; +- +- t = mktime(&stm); +- +- if (tz_env) +- setenv("TZ", tz_orig, 1); +- else +- unsetenv("TZ"); +- tzset(); +- +- if (t == -1) +- return tz_leap; +- +- if (stm.tm_sec == 60) +- tz_leap = LEAP_InsertSecond; +- else if (stm.tm_sec == 1) +- tz_leap = LEAP_DeleteSecond; +- +- *tai_offset = tz_tai_offset; +- +- return tz_leap; +-} +- +-/* ================================================== */ +- + static void + leap_end_timeout(void *arg) + { +@@ -751,16 +664,16 @@ 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 ldb_leap; + int leap_sec, tai_offset; + + leap_sec = 0; + tai_offset = 0; + +- if (leap_tzname && now) { +- tz_leap = get_tz_leap(now, &tai_offset); ++ if (now) { ++ ldb_leap = LDB_GetLeap(now, &tai_offset); + if (leap == LEAP_Normal) +- leap = tz_leap; ++ leap = ldb_leap; + } + + if (leap == LEAP_InsertSecond || leap == LEAP_DeleteSecond) { +@@ -1398,7 +1311,7 @@ REF_GetTaiOffset(struct timespec *ts) + { + int tai_offset; + +- get_tz_leap(ts->tv_sec, &tai_offset); ++ LDB_GetLeap(ts->tv_sec, &tai_offset); + + return tai_offset; + } diff --git a/02ae9a86077c629eb6f998c4f824bb9b9c4e60ce.patch b/02ae9a86077c629eb6f998c4f824bb9b9c4e60ce.patch new file mode 100644 index 0000000..52068d8 --- /dev/null +++ b/02ae9a86077c629eb6f998c4f824bb9b9c4e60ce.patch @@ -0,0 +1,88 @@ +From 02ae9a86077c629eb6f998c4f824bb9b9c4e60ce Mon Sep 17 00:00:00 2001 +From: Patrick Oppenlander +Date: Thu, 8 Feb 2024 14:36:26 +1100 +Subject: [PATCH] leapdb: make twice per day check logic common + +We want to do the twice per day check regardless of the data source. +Move the check up one level from get_tz_leap() into LDB_GetLeap(). +--- + leapdb.c | 41 ++++++++++++++++++++--------------------- + 1 file changed, 20 insertions(+), 21 deletions(-) + +diff --git a/leapdb.c b/leapdb.c +index 676a0d5..32f753a 100644 +--- a/leapdb.c ++++ b/leapdb.c +@@ -41,24 +41,10 @@ static char *leap_tzname; + static NTP_Leap + get_tz_leap(time_t when, int *tai_offset) + { +- static time_t last_tz_leap_check; +- static NTP_Leap tz_leap; +- static int tz_tai_offset; +- + struct tm stm, *tm; + time_t t; + char *tz_env, tz_orig[128]; +- +- *tai_offset = tz_tai_offset; +- +- /* Do this check at most twice a day */ +- when = when / (12 * 3600) * (12 * 3600); +- if (last_tz_leap_check == when) +- return tz_leap; +- +- last_tz_leap_check = when; +- tz_leap = LEAP_Normal; +- tz_tai_offset = 0; ++ NTP_Leap tz_leap = LEAP_Normal; + + tm = gmtime(&when); + if (!tm) +@@ -79,7 +65,7 @@ get_tz_leap(time_t when, int *tai_offset) + /* Get the TAI-UTC offset, which started at the epoch at 10 seconds */ + t = mktime(&stm); + if (t != -1) +- tz_tai_offset = t - when + 10; ++ *tai_offset = t - when + 10; + + /* Set the time to 23:59:60 and see how it overflows in mktime() */ + stm.tm_sec = 60; +@@ -102,8 +88,6 @@ get_tz_leap(time_t when, int *tai_offset) + else if (stm.tm_sec == 1) + tz_leap = LEAP_DeleteSecond; + +- *tai_offset = tz_tai_offset; +- + return tz_leap; + } + +@@ -132,10 +116,25 @@ LDB_Initialise(void) + NTP_Leap + LDB_GetLeap(time_t when, int *tai_offset) + { +- *tai_offset = 0; ++ static time_t last_ldb_leap_check; ++ static NTP_Leap ldb_leap; ++ static int ldb_tai_offset; ++ ++ /* Do this check at most twice a day */ ++ when = when / (12 * 3600) * (12 * 3600); ++ if (last_ldb_leap_check == when) ++ goto out; ++ ++ last_ldb_leap_check = when; ++ ldb_leap = LEAP_Normal; ++ ldb_tai_offset = 0; ++ + if (leap_tzname) +- return get_tz_leap(when, tai_offset); +- return LEAP_Normal; ++ ldb_leap = get_tz_leap(when, &ldb_tai_offset); ++ ++out: ++ *tai_offset = ldb_tai_offset; ++ return ldb_leap; + } + + /* ================================================== */ diff --git a/53823b9f1c076b3b26d0ad031eb95a51e6842abd.patch b/53823b9f1c076b3b26d0ad031eb95a51e6842abd.patch new file mode 100644 index 0000000..475a14b --- /dev/null +++ b/53823b9f1c076b3b26d0ad031eb95a51e6842abd.patch @@ -0,0 +1,317 @@ +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 73fe774a..6eae11c9 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 58ebdeb0..4c0a7879 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 eeaa5010..bd296bc6 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 aa49b3c4..e748e001 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 84f7439c..44ba6d5c 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); diff --git a/637b77d1bd634298b1b54059e212d6f7d402fa26.patch b/637b77d1bd634298b1b54059e212d6f7d402fa26.patch new file mode 100644 index 0000000..a2a07ab --- /dev/null +++ b/637b77d1bd634298b1b54059e212d6f7d402fa26.patch @@ -0,0 +1,150 @@ +From 637b77d1bd634298b1b54059e212d6f7d402fa26 Mon Sep 17 00:00:00 2001 +From: Patrick Oppenlander +Date: Thu, 8 Feb 2024 14:36:29 +1100 +Subject: [PATCH] test: add leapdb unit test + +--- + test/unit/leapdb.c | 104 ++++++++++++++++++++++++++++++++++++++++++ + test/unit/leapdb.list | 22 +++++++++ + 2 files changed, 126 insertions(+) + create mode 100644 test/unit/leapdb.c + create mode 100644 test/unit/leapdb.list + +diff --git a/test/unit/leapdb.c b/test/unit/leapdb.c +new file mode 100644 +index 0000000..509fc39 +--- /dev/null ++++ b/test/unit/leapdb.c +@@ -0,0 +1,104 @@ ++/* ++ ********************************************************************** ++ * Copyright (C) Patrick Oppenlander 2023 ++ * ++ * 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 ++ * published by the Free Software Foundation. ++ * ++ * This program is distributed in the hope that it will be useful, but ++ * WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ++ * General Public License for more details. ++ * ++ * You should have received a copy of the GNU General Public License along ++ * with this program; if not, write to the Free Software Foundation, Inc., ++ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. ++ * ++ ********************************************************************** ++ */ ++ ++#include ++#include "test.h" ++ ++struct test_vector { ++ time_t when; ++ int tai_offset; ++ NTP_Leap leap; ++ int fake; ++} tests[] = { ++ /* leapdb.list is a cut down version of leap-seconds.list */ ++ {3439756800, 34, LEAP_InsertSecond, 0}, /* 1 Jan 2009 */ ++ {3550089600, 35, LEAP_InsertSecond, 0}, /* 1 Jul 2012 */ ++ {3644697600, 36, LEAP_InsertSecond, 0}, /* 1 Jul 2015 */ ++ {3692217600, 37, LEAP_InsertSecond, 0}, /* 1 Jan 2017 */ ++ {3786825600, 36, LEAP_DeleteSecond, 1}, /* 1 Jan 2020 fake in leapdb.list */ ++}; ++ ++static void ++test_leap_source(NTP_Leap (*fn)(time_t when, int *tai_offset), ++ int skip_fakes) ++{ ++ int prev_tai_offset = 34; ++ for (int i = 0; i < sizeof tests / sizeof tests[0]; ++i) { ++ struct test_vector *t = tests + i; ++ ++ NTP_Leap leap; ++ int tai_offset = -1; ++ ++ /* Our unit test leapdb.list contains a fake entry removing a leap second. ++ * Skip this when testing with the right/UTC timezone using mktime(). */ ++ if (skip_fakes && t->fake) ++ continue; ++ ++ /* One second before leap second */ ++ leap = fn(t->when - LEAP_SEC_LIST_OFFSET - 1, &tai_offset); ++ TEST_CHECK(leap == t->leap); ++ TEST_CHECK(tai_offset = prev_tai_offset); ++ ++ /* Exactly on leap second */ ++ leap = fn(t->when - LEAP_SEC_LIST_OFFSET, &tai_offset); ++ TEST_CHECK(leap == LEAP_Normal); ++ TEST_CHECK(tai_offset == t->tai_offset); ++ ++ /* One second after leap second */ ++ leap = fn(t->when - LEAP_SEC_LIST_OFFSET + 1, &tai_offset); ++ TEST_CHECK(leap == LEAP_Normal); ++ TEST_CHECK(tai_offset == t->tai_offset); ++ ++ prev_tai_offset = t->tai_offset; ++ } ++} ++ ++void ++test_unit(void) ++{ ++ char conf[][100] = { ++ "leapsectz right/UTC", ++ "leapseclist leapdb.list" ++ }; ++ ++ CNF_Initialise(0, 0); ++ for (int i = 0; i < sizeof conf / sizeof conf[0]; i++) ++ CNF_ParseLine(NULL, i + 1, conf[i]); ++ LDB_Initialise(); ++ ++ if (check_leap_source(get_tz_leap)) { ++ DEBUG_LOG("testing get_tz_leap"); ++ test_leap_source(get_tz_leap, 1); ++ } else { ++ DEBUG_LOG("Skipping get_tz_leap test. Either the right/UTC timezone is " ++ "missing, or mktime() doesn't support leap seconds."); ++ } ++ ++ DEBUG_LOG("testing get_list_leap"); ++ TEST_CHECK(check_leap_source(get_list_leap)); ++ test_leap_source(get_list_leap, 0); ++ ++ /* This exercises the twice-per-day logic */ ++ DEBUG_LOG("testing LDB_GetLeap"); ++ test_leap_source(LDB_GetLeap, 1); ++ ++ LDB_Finalise(); ++ CNF_Finalise(); ++} +diff --git a/test/unit/leapdb.list b/test/unit/leapdb.list +new file mode 100644 +index 0000000..5dc2188 +--- /dev/null ++++ b/test/unit/leapdb.list +@@ -0,0 +1,22 @@ ++# ++# Cut down version of leap-seconds.list for unit test. ++# ++# Blank lines need to be ignored, so include a few for testing. ++# Whitespace errors on non-blank lines below are copied from the original file. ++# ++ ++# Leap second data update time ++#$ 3676924800 ++# ++# File update time ++#@ 3928521600 ++ ++3439756800 34 # 1 Jan 2009 ++3550089600 35 # 1 Jul 2012 ++3644697600 36 # 1 Jul 2015 ++3692217600 37 # 1 Jan 2017 ++3786825600 36 # 1 Jan 2020 (fake entry to test negative leap second) ++ ++# FIPS 180-1 hash ++# NOTE! this value has not been recomputed for this unit test file. ++#h 16edd0f0 3666784f 37db6bdd e74ced87 59af48f1 diff --git a/83f90279b0fdd64c1d67d479de5e60ee068b9499.patch b/83f90279b0fdd64c1d67d479de5e60ee068b9499.patch new file mode 100644 index 0000000..2a40be8 --- /dev/null +++ b/83f90279b0fdd64c1d67d479de5e60ee068b9499.patch @@ -0,0 +1,59 @@ +From 83f90279b0fdd64c1d67d479de5e60ee068b9499 Mon Sep 17 00:00:00 2001 +From: Patrick Oppenlander +Date: Thu, 8 Feb 2024 14:36:27 +1100 +Subject: [PATCH] leapdb: move source check into separate function + +The sanity checks are valid for all possible sources of leap second +information, so move them into a separate function check_leap_source(). +--- + leapdb.c | 32 +++++++++++++++++++++----------- + 1 file changed, 21 insertions(+), 11 deletions(-) + +diff --git a/leapdb.c b/leapdb.c +index 32f753a..aa49b3c 100644 +--- a/leapdb.c ++++ b/leapdb.c +@@ -93,22 +93,32 @@ get_tz_leap(time_t when, int *tai_offset) + + /* ================================================== */ + ++static int ++check_leap_source(NTP_Leap (*src)(time_t when, int *tai_offset)) ++{ ++ int tai_offset = 0; ++ ++ /* Check that the leap second source has good data for Jun 30 2012 and Dec 31 2012 */ ++ if (src(1341014400, &tai_offset) == LEAP_InsertSecond && tai_offset == 34 && ++ src(1356912000, &tai_offset) == LEAP_Normal && tai_offset == 35) ++ return 1; ++ ++ return 0; ++} ++ ++/* ================================================== */ ++ + void + LDB_Initialise(void) + { +- int tai_offset; +- + leap_tzname = CNF_GetLeapSecTimezone(); +- if (leap_tzname) { +- /* Check that the timezone has good data for Jun 30 2012 and Dec 31 2012 */ +- if (get_tz_leap(1341014400, &tai_offset) == LEAP_InsertSecond && tai_offset == 34 && +- get_tz_leap(1356912000, &tai_offset) == LEAP_Normal && tai_offset == 35) { +- LOG(LOGS_INFO, "Using %s timezone to obtain leap second data", leap_tzname); +- } else { +- LOG(LOGS_WARN, "Timezone %s failed leap second check, ignoring", leap_tzname); +- leap_tzname = NULL; +- } ++ 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) ++ LOG(LOGS_INFO, "Using %s timezone to obtain leap second data", leap_tzname); + } + + /* ================================================== */ diff --git a/chrony.spec b/chrony.spec index afdaf20..e09a654 100644 --- a/chrony.spec +++ b/chrony.spec @@ -70,7 +70,12 @@ Patch2: chrony-logrotate.patch Patch3: chrony-service-ordering.patch Patch7: chrony-htonl.patch Patch8: chrony.nm-dispatcher.dhcp.patch -Patch9: 6cf9fe2f16fa49963e47e84f4a6dd9069735062e.patch +Patch9: 017d6f8f5630071e2a532f58f9513757d94ac7d5.patch +Patch10: 02ae9a86077c629eb6f998c4f824bb9b9c4e60ce.patch +Patch11: 83f90279b0fdd64c1d67d479de5e60ee068b9499.patch +Patch12: 53823b9f1c076b3b26d0ad031eb95a51e6842abd.patch +Patch13: 637b77d1bd634298b1b54059e212d6f7d402fa26.patch +Patch14: 6cf9fe2f16fa49963e47e84f4a6dd9069735062e.patch BuildRequires: NetworkManager-devel BuildRequires: bison BuildRequires: findutils @@ -87,6 +92,7 @@ BuildRequires: pps-tools-devel BuildRequires: sysuser-tools BuildRequires: timezone BuildRequires: pkgconfig(systemd) +BuildRequires: rubygem(asciidoctor) Recommends: logrotate Requires(post): %fillup_prereq %if %{with sysusers} @@ -183,6 +189,11 @@ e.g. because the servers will be set via DHCP. %patch -P 7 %patch -P 8 %patch -P 9 -p1 +%patch -P 10 -p1 +%patch -P 11 -p1 +%patch -P 12 -p1 +%patch -P 13 -p1 +%patch -P 14 -p1 # Remove pool statements from the default /etc/chrony.conf. They will # be provided by branding packages in /etc/chrony.d/pool.conf .