reference: get TAI-UTC offset from leap second timezone

Use the timezone specified by the leapsectz directive to get the
current TAI-UTC offset and set the offset of the system clock in order
to provide correct TAI time to applications using ntp_adjtime(),
ntp_gettime(), or clock_gettime(CLOCK_TAI).
This commit is contained in:
Miroslav Lichvar 2017-06-29 17:56:16 +02:00
parent cde0a20307
commit 29b0ad894c
2 changed files with 52 additions and 22 deletions

View file

@ -830,15 +830,23 @@ clients to safely synchronise with multiple identically configured leap
smearing servers. smearing servers.
[[leapsectz]]*leapsectz* _timezone_:: [[leapsectz]]*leapsectz* _timezone_::
This directive is used to set the name of the timezone in the system tz This directive specifies a timezone in the system tz database which *chronyd*
database which *chronyd* can use to find out when will the next leap second can use to determine when will the next leap second occur and what is the
occur. It will periodically check if the times 23:59:59 and 23:59:60 are valid current offset between TAI and UTC. It will periodically check if 23:59:59 and
on Jun 30 and Dec 31 in the timezone. This typically works with the *right/UTC* 23:59:60 are valid times in the timezone. This typically works with the
timezone. _right/UTC_ timezone.
+ +
This directive is mainly useful with reference clocks which do not provide When a leap second is announced, the timezone needs to be updated at least 12
leap second information. It is not necessary to restart *chronyd* if the tz hours before the leap second. It is not necessary to restart *chronyd*.
database is updated with a new leap second at least 12 hours before the event. +
This directive is useful with reference clocks and other time sources which do
not announce leap seconds, or announce them too late for an NTP server to
forward them to its own clients. Clients of leap smearing servers must not
use this directive.
+
It is also useful when the system clock is required to have correct TAI-UTC
offset. Note that the offset is set only when leap seconds are handled by the
kernel, i.e. <<leapsecmode,*leapsecmode*>> is set to *system*.
+ +
An example of the directive is: An example of the directive is:
+ +
@ -2325,7 +2333,7 @@ The amount of memory used for logging client accesses can be increased in order
to enable clients to use the interleaved mode even when the server has a large to enable clients to use the interleaved mode even when the server has a large
number of clients, and better support rate limiting if it is enabled by the number of clients, and better support rate limiting if it is enabled by the
<<ratelimit,*ratelimit*>> directive. The system timezone database, if it is <<ratelimit,*ratelimit*>> directive. The system timezone database, if it is
kept up to date and includes the *right/UTC* timezone, can be used as a kept up to date and includes the _right/UTC_ timezone, can be used as a
reliable source to determine when a leap second will be applied to UTC. The reliable source to determine when a leap second will be applied to UTC. The
*-r* option with the <<dumpdir,*dumpdir*>> directive shortens the time in which *-r* option with the <<dumpdir,*dumpdir*>> directive shortens the time in which
*chronyd* will not be able to serve time to its clients when it needs to be *chronyd* will not be able to serve time to its clients when it needs to be

View file

@ -49,6 +49,7 @@ static int local_orphan;
static double local_distance; static double local_distance;
static NTP_Leap our_leap_status; static NTP_Leap our_leap_status;
static int our_leap_sec; static int our_leap_sec;
static int our_tai_offset;
static int our_stratum; static int our_stratum;
static uint32_t our_ref_id; static uint32_t our_ref_id;
static IPAddr our_ref_ip; static IPAddr our_ref_ip;
@ -139,7 +140,7 @@ static double last_ref_update_interval;
/* ================================================== */ /* ================================================== */
static NTP_Leap get_tz_leap(time_t when); 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); static void update_leap_status(NTP_Leap leap, time_t now, int reset);
/* ================================================== */ /* ================================================== */
@ -179,11 +180,13 @@ REF_Initialise(void)
FILE *in; FILE *in;
double file_freq_ppm, file_skew_ppm; double file_freq_ppm, file_skew_ppm;
double our_frequency_ppm; double our_frequency_ppm;
int tai_offset;
mode = REF_ModeNormal; mode = REF_ModeNormal;
are_we_synchronised = 0; are_we_synchronised = 0;
our_leap_status = LEAP_Unsynchronised; our_leap_status = LEAP_Unsynchronised;
our_leap_sec = 0; our_leap_sec = 0;
our_tai_offset = 0;
initialised = 1; initialised = 1;
our_root_dispersion = 1.0; our_root_dispersion = 1.0;
our_root_delay = 1.0; our_root_delay = 1.0;
@ -241,8 +244,8 @@ REF_Initialise(void)
leap_tzname = CNF_GetLeapSecTimezone(); leap_tzname = CNF_GetLeapSecTimezone();
if (leap_tzname) { if (leap_tzname) {
/* Check that the timezone has good data for Jun 30 2012 and Dec 31 2012 */ /* Check that the timezone has good data for Jun 30 2012 and Dec 31 2012 */
if (get_tz_leap(1341014400) == LEAP_InsertSecond && if (get_tz_leap(1341014400, &tai_offset) == LEAP_InsertSecond && tai_offset == 34 &&
get_tz_leap(1356912000) == LEAP_Normal) { get_tz_leap(1356912000, &tai_offset) == LEAP_Normal && tai_offset == 35) {
LOG(LOGS_INFO, "Using %s timezone to obtain leap second data", leap_tzname); LOG(LOGS_INFO, "Using %s timezone to obtain leap second data", leap_tzname);
} else { } else {
LOG(LOGS_WARN, "Timezone %s failed leap second check, ignoring", leap_tzname); LOG(LOGS_WARN, "Timezone %s failed leap second check, ignoring", leap_tzname);
@ -613,14 +616,18 @@ is_leap_second_day(struct tm *stm) {
/* ================================================== */ /* ================================================== */
static NTP_Leap static NTP_Leap
get_tz_leap(time_t when) get_tz_leap(time_t when, int *tai_offset)
{ {
static time_t last_tz_leap_check; static time_t last_tz_leap_check;
static NTP_Leap tz_leap; static NTP_Leap tz_leap;
static int tz_tai_offset;
struct tm stm; struct tm stm;
time_t t; time_t t;
char *tz_env, tz_orig[128]; char *tz_env, tz_orig[128];
*tai_offset = tz_tai_offset;
/* Do this check at most twice a day */ /* Do this check at most twice a day */
when = when / (12 * 3600) * (12 * 3600); when = when / (12 * 3600) * (12 * 3600);
if (last_tz_leap_check == when) if (last_tz_leap_check == when)
@ -628,12 +635,10 @@ get_tz_leap(time_t when)
last_tz_leap_check = when; last_tz_leap_check = when;
tz_leap = LEAP_Normal; tz_leap = LEAP_Normal;
tz_tai_offset = 0;
stm = *gmtime(&when); stm = *gmtime(&when);
if (!is_leap_second_day(&stm))
return tz_leap;
/* Temporarily switch to the timezone containing leap seconds */ /* Temporarily switch to the timezone containing leap seconds */
tz_env = getenv("TZ"); tz_env = getenv("TZ");
if (tz_env) { if (tz_env) {
@ -644,6 +649,11 @@ get_tz_leap(time_t when)
setenv("TZ", leap_tzname, 1); setenv("TZ", leap_tzname, 1);
tzset(); 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() */ /* Set the time to 23:59:60 and see how it overflows in mktime() */
stm.tm_sec = 60; stm.tm_sec = 60;
stm.tm_min = 59; stm.tm_min = 59;
@ -665,6 +675,8 @@ get_tz_leap(time_t when)
else if (stm.tm_sec == 1) else if (stm.tm_sec == 1)
tz_leap = LEAP_DeleteSecond; tz_leap = LEAP_DeleteSecond;
*tai_offset = tz_tai_offset;
return tz_leap; return tz_leap;
} }
@ -675,10 +687,13 @@ leap_end_timeout(void *arg)
{ {
leap_timeout_id = 0; leap_timeout_id = 0;
leap_in_progress = 0; leap_in_progress = 0;
if (our_tai_offset)
our_tai_offset += our_leap_sec;
our_leap_sec = 0; our_leap_sec = 0;
if (leap_mode == REF_LeapModeSystem) if (leap_mode == REF_LeapModeSystem)
LCL_SetSystemLeap(our_leap_sec, 0); LCL_SetSystemLeap(our_leap_sec, our_tai_offset);
if (our_leap_status == LEAP_InsertSecond || if (our_leap_status == LEAP_InsertSecond ||
our_leap_status == LEAP_DeleteSecond) our_leap_status == LEAP_DeleteSecond)
@ -752,12 +767,17 @@ set_leap_timeout(time_t now)
static void static void
update_leap_status(NTP_Leap leap, time_t now, int reset) update_leap_status(NTP_Leap leap, time_t now, int reset)
{ {
int leap_sec; NTP_Leap tz_leap;
int leap_sec, tai_offset;
leap_sec = 0; leap_sec = 0;
tai_offset = 0;
if (leap_tzname && now && leap == LEAP_Normal) if (leap_tzname && now) {
leap = get_tz_leap(now); tz_leap = get_tz_leap(now, &tai_offset);
if (leap == LEAP_Normal)
leap = tz_leap;
}
if (leap == LEAP_InsertSecond || leap == LEAP_DeleteSecond) { if (leap == LEAP_InsertSecond || leap == LEAP_DeleteSecond) {
/* Check that leap second is allowed today */ /* Check that leap second is allowed today */
@ -773,12 +793,14 @@ update_leap_status(NTP_Leap leap, time_t now, int reset)
} }
} }
if (leap_sec != our_leap_sec && !REF_IsLeapSecondClose()) { if ((leap_sec != our_leap_sec || tai_offset != our_tai_offset)
&& !REF_IsLeapSecondClose()) {
our_leap_sec = leap_sec; our_leap_sec = leap_sec;
our_tai_offset = tai_offset;
switch (leap_mode) { switch (leap_mode) {
case REF_LeapModeSystem: case REF_LeapModeSystem:
LCL_SetSystemLeap(our_leap_sec, 0); LCL_SetSystemLeap(our_leap_sec, our_tai_offset);
/* Fall through */ /* Fall through */
case REF_LeapModeSlew: case REF_LeapModeSlew:
case REF_LeapModeStep: case REF_LeapModeStep: