From 2a5c045c3dc2a8a1a69854e793c155a07ffaca7d Mon Sep 17 00:00:00 2001 From: Miroslav Lichvar Date: Thu, 23 Feb 2012 18:48:24 +0100 Subject: [PATCH] Add support for reading leap second data from tz database leapsectz directive is used to set the name of the timezone in the system tz database which chronyd can use to find out when will the next leap second occur. It will periodically check if dates Jun 30 23:59:60 and Dec 31 23:59:60 are valid in that timezone. This is mainly useful with reference clocks which don't provide the leap second information. It is not necessary to restart chronyd if the tz database is updated with a new leap second at least 12 hours before the event. --- chrony.texi | 30 +++++++++++++++--- conf.c | 23 ++++++++++++++ conf.h | 1 + reference.c | 87 ++++++++++++++++++++++++++++++++++++++++++++++++----- 4 files changed, 128 insertions(+), 13 deletions(-) diff --git a/chrony.texi b/chrony.texi index 9b75eff..b09da92 100644 --- a/chrony.texi +++ b/chrony.texi @@ -1183,6 +1183,7 @@ directives can occur in any order in the file. * include directive:: Include a configuration file * initstepslew directive:: Trim the system clock on boot-up. * keyfile directive:: Specify location of file containing keys +* leapsectz directive:: Read leap second data from tz database * linux_hz directive:: Define a non-standard value of the kernel HZ constant * linux_freq_scale directive:: Define a non-standard value to compensate the kernel frequency bias * local directive:: Allow unsynchronised machine to act as server @@ -1744,6 +1745,25 @@ prefix. The ID for the chronyc authentication key is specified with the commandkey command (see earlier). +@c }}} +@c {{{ leapsectz +@node leapsectz directive +@subsection leapsectz +This directive is used to set the name of the timezone in the system +tz database which @code{chronyd} can use to find out when will the +next leap second occur. It will periodically check if the times +23:59:59 and 23:59:60 are valid on Jun 30 and Dec 31 in the timezone. +This is mainly useful with reference clocks which don't provide the +leap second information. It is not necessary to restart +@code{chronyd} if the tz database is updated with a new leap second at +least 12 hours before the event. + +An example of the command is + +@example +leapsectz right/UTC +@end example + @c }}} @c {{{ local @node local directive @@ -1894,9 +1914,9 @@ expressed in UTC, not the local time zone. IP address of server/peer from which measurement comes [158.152.1.76] @item Leap status (@code{N} means normal, @code{+} means that the last minute -of today has 61 seconds, @code{-} means that the last minute of the day -has 59 seconds, @code{?} means the remote computer is not currently -synchronised.) [N] +of the current month has 61 seconds, @code{-} means that the last minute +of the month has 59 seconds, @code{?} means the remote computer is not +currently synchronised.) [N] @item Stratum of remote computer. [2] @item @@ -2110,8 +2130,8 @@ Sequence number of driver poll within one polling interval for raw samples, or @code{-} for filtered samples. [7] @item Leap status (@code{N} means normal, @code{+} means that the last minute -of today has 61 seconds, @code{-} means that the last minute of the day -has 59 seconds). [N] +of the current month has 61 seconds, @code{-} means that the last minute +of the month has 59 seconds). [N] @item Flag indicating whether the sample comes from PPS source. (1 for yes, 0 for no, or @code{-} for filtered sample). [1] diff --git a/conf.c b/conf.c index bbd890c..460b42f 100644 --- a/conf.c +++ b/conf.c @@ -112,6 +112,7 @@ static void parse_sched_priority(const char *); static void parse_lockall(const char *); static void parse_tempcomp(const char *); static void parse_include(const char *); +static void parse_leapsectz(const char *); /* ================================================== */ /* Configuration variables */ @@ -224,6 +225,9 @@ static double linux_freq_scale; static int sched_priority = 0; static int lock_memory = 0; +/* Name of a system timezone containing leap seconds occuring at midnight */ +static char *leapsec_tz = NULL; + /* ================================================== */ typedef struct { @@ -276,6 +280,7 @@ static const Command commands[] = { {"reselectdist", 12, parse_reselectdist}, {"stratumweight", 13, parse_stratumweight}, {"include", 7, parse_include}, + {"leapsectz", 9, parse_leapsectz}, {"linux_hz", 8, parse_linux_hz}, {"linux_freq_scale", 16, parse_linux_freq_scale}, {"sched_priority", 14, parse_sched_priority}, @@ -1282,6 +1287,16 @@ parse_include(const char *line) /* ================================================== */ +static void +parse_leapsectz(const char *line) +{ + /* This must allocate enough space! */ + leapsec_tz = MallocArray(char, 1 + strlen(line)); + sscanf(line, "%s", leapsec_tz); +} + +/* ================================================== */ + static void parse_linux_hz(const char *line) { @@ -1707,6 +1722,14 @@ CNF_GetPidFile(void) /* ================================================== */ +char * +CNF_GetLeapSecTimezone(void) +{ + return leapsec_tz; +} + +/* ================================================== */ + void CNF_GetLinuxHz(int *set, int *hz) { diff --git a/conf.h b/conf.h index 7f0f6b3..15d24d7 100644 --- a/conf.h +++ b/conf.h @@ -69,6 +69,7 @@ extern void CNF_GetFallbackDrifts(int *min, int *max); extern void CNF_GetBindAddress(int family, IPAddr *addr); extern void CNF_GetBindCommandAddress(int family, IPAddr *addr); extern char *CNF_GetPidFile(void); +extern char *CNF_GetLeapSecTimezone(void); extern void CNF_GetLinuxHz(int *set, int *hz); extern void CNF_GetLinuxFreqScale(int *set, double *freq_scale); diff --git a/reference.c b/reference.c index 7bf8ec7..0310d20 100644 --- a/reference.c +++ b/reference.c @@ -89,6 +89,11 @@ static double drift_file_age; static void update_drift_file(double, double); +/* Name of a system timezone containing leap seconds occuring at midnight */ +static char *leap_tzname; +static time_t last_tz_leap_check; +static NTP_Leap tz_leap; + /* ================================================== */ static LOG_FileID logfileid; @@ -196,6 +201,8 @@ REF_Initialise(void) enable_local_stratum = CNF_AllowLocalReference(&local_stratum); + leap_tzname = CNF_GetLeapSecTimezone(); + CNF_GetMakeStep(&make_step_limit, &make_step_threshold); CNF_GetMaxChange(&max_offset_delay, &max_offset_ignore, &max_offset); CNF_GetLogChange(&do_log_change, &log_change_threshold); @@ -526,20 +533,82 @@ is_offset_ok(double offset) /* ================================================== */ -static void -update_leap_status(NTP_Leap leap) +static NTP_Leap +get_tz_leap(time_t when) +{ + struct tm stm; + time_t t; + char *tz_env, tz_orig[128]; + + /* 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; + + stm = *gmtime(&when); + + /* Check for leap second only in the latter half of June and December */ + if (stm.tm_mon == 5 && stm.tm_mday > 14) + stm.tm_mday = 30; + else if (stm.tm_mon == 11 && stm.tm_mday > 14) + stm.tm_mday = 31; + else + return tz_leap; + + /* 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(); + + /* 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; + + return tz_leap; +} + +/* ================================================== */ + +static void +update_leap_status(NTP_Leap leap, time_t now) { - time_t now; struct tm stm; int leap_sec; leap_sec = 0; + if (leap_tzname && now && leap == LEAP_Normal) + leap = get_tz_leap(now); + if (leap == LEAP_InsertSecond || leap == LEAP_DeleteSecond) { /* Insert/delete leap second only on June 30 or December 31 and in other months ignore the leap status completely */ - now = time(NULL); stm = *gmtime(&now); if (stm.tm_mon != 5 && stm.tm_mon != 11) { @@ -600,7 +669,7 @@ REF_SetReference(int stratum, double update_interval; double elapsed; double correction_rate; - struct timeval now; + struct timeval now, raw_now; assert(initialised); @@ -628,7 +697,9 @@ REF_SetReference(int stratum, } } - LCL_ReadCookedTime(&now, NULL); + LCL_ReadRawTime(&raw_now); + LCL_CookTime(&raw_now, &now, NULL); + UTI_DiffTimevalsToDouble(&elapsed, &now, ref_time); our_offset = offset + elapsed * frequency; @@ -646,7 +717,7 @@ REF_SetReference(int stratum, our_root_delay = root_delay; our_root_dispersion = root_dispersion; - update_leap_status(leap); + update_leap_status(leap, raw_now.tv_sec); if (last_ref_update.tv_sec) { UTI_DiffTimevalsToDouble(&update_interval, &now, &last_ref_update); @@ -827,7 +898,7 @@ REF_SetUnsynchronised(void) are_we_synchronised = 0; - update_leap_status(LEAP_Unsynchronised); + update_leap_status(LEAP_Unsynchronised, 0); } /* ================================================== */