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.
This commit is contained in:
Patrick Oppenlander 2024-02-08 14:36:28 +11:00 committed by Miroslav Lichvar
parent 83f90279b0
commit 53823b9f1c
5 changed files with 155 additions and 10 deletions

14
conf.c
View file

@ -249,6 +249,9 @@ static REF_LeapMode leapsec_mode = REF_LeapModeSystem;
/* Name of a system timezone containing leap seconds occuring at midnight */ /* Name of a system timezone containing leap seconds occuring at midnight */
static char *leapsec_tz = NULL; 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. */ /* Name of the user to which will be dropped root privileges. */
static char *user; static char *user;
@ -471,6 +474,7 @@ CNF_Finalise(void)
Free(hwclock_file); Free(hwclock_file);
Free(keys_file); Free(keys_file);
Free(leapsec_tz); Free(leapsec_tz);
Free(leapsec_list);
Free(logdir); Free(logdir);
Free(bind_ntp_iface); Free(bind_ntp_iface);
Free(bind_acq_iface); Free(bind_acq_iface);
@ -620,6 +624,8 @@ CNF_ParseLine(const char *filename, int number, char *line)
parse_leapsecmode(p); parse_leapsecmode(p);
} else if (!strcasecmp(command, "leapsectz")) { } else if (!strcasecmp(command, "leapsectz")) {
parse_string(p, &leapsec_tz); parse_string(p, &leapsec_tz);
} else if (!strcasecmp(command, "leapseclist")) {
parse_string(p, &leapsec_list);
} else if (!strcasecmp(command, "local")) { } else if (!strcasecmp(command, "local")) {
parse_local(p); parse_local(p);
} else if (!strcasecmp(command, "lock_all")) { } else if (!strcasecmp(command, "lock_all")) {
@ -2389,6 +2395,14 @@ CNF_GetLeapSecTimezone(void)
/* ================================================== */ /* ================================================== */
char *
CNF_GetLeapSecList(void)
{
return leapsec_list;
}
/* ================================================== */
int int
CNF_GetSchedPriority(void) CNF_GetSchedPriority(void)
{ {

1
conf.h
View file

@ -91,6 +91,7 @@ extern char *CNF_GetNtpSigndSocket(void);
extern char *CNF_GetPidFile(void); extern char *CNF_GetPidFile(void);
extern REF_LeapMode CNF_GetLeapSecMode(void); extern REF_LeapMode CNF_GetLeapSecMode(void);
extern char *CNF_GetLeapSecTimezone(void); extern char *CNF_GetLeapSecTimezone(void);
extern char *CNF_GetLeapSecList(void);
/* Value returned in ppm, as read from file */ /* Value returned in ppm, as read from file */
extern double CNF_GetMaxUpdateSkew(void); extern double CNF_GetMaxUpdateSkew(void);

View file

@ -680,9 +680,10 @@ trusted and required source.
*tai*::: *tai*:::
This option indicates that the reference clock keeps time in TAI instead of UTC 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 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 <<leapsectz,*leapsectz*>> or <<leapseclist,*leapseclist*>> directive must be
database must be kept up to date in order for this correction to work as used with this option and the database must be kept up to date in order for
expected. This option does not make sense with PPS refclocks. this correction to work as expected. This option does not make sense with PPS
refclocks.
*local*::: *local*:::
This option specifies that the reference clock is an unsynchronised clock which 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 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 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
<<leapsectz,*leapsectz*>>.
+
An example of this directive is:
+
----
leapseclist /usr/share/zoneinfo/leap-seconds.list
----
[[makestep]]*makestep* _threshold_ _limit_:: [[makestep]]*makestep* _threshold_ _limit_::
Normally *chronyd* will cause the system to gradually correct any time offset, 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, by slowing down or speeding up the clock as required. In certain situations,

126
leapdb.c
View file

@ -3,6 +3,7 @@
********************************************************************** **********************************************************************
* Copyright (C) Miroslav Lichvar 2009-2018, 2020, 2022 * 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 * 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 * it under the terms of version 2 of the GNU General Public License as
@ -30,11 +31,20 @@
#include "conf.h" #include "conf.h"
#include "leapdb.h" #include "leapdb.h"
#include "logging.h" #include "logging.h"
#include "util.h"
/* ================================================== */ /* ================================================== */
/* Name of a system timezone containing leap seconds occuring at midnight */ /* Source of leap second data */
static char *leap_tzname; 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; return tz_leap;
strcpy(tz_orig, tz_env); strcpy(tz_orig, tz_env);
} }
setenv("TZ", leap_tzname, 1); setenv("TZ", CNF_GetLeapSecTimezone(), 1);
tzset(); tzset();
/* Get the TAI-UTC offset, which started at the epoch at 10 seconds */ /* 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 static int
check_leap_source(NTP_Leap (*src)(time_t when, int *tai_offset)) 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 void
LDB_Initialise(void) LDB_Initialise(void)
{ {
const char *leap_tzname, *leap_sec_list;
leap_tzname = CNF_GetLeapSecTimezone(); leap_tzname = CNF_GetLeapSecTimezone();
if (leap_tzname && !check_leap_source(get_tz_leap)) { if (leap_tzname && !check_leap_source(get_tz_leap)) {
LOG(LOGS_WARN, "Timezone %s failed leap second check, ignoring", leap_tzname); LOG(LOGS_WARN, "Timezone %s failed leap second check, ignoring", leap_tzname);
leap_tzname = NULL; 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); 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_leap = LEAP_Normal;
ldb_tai_offset = 0; 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); ldb_leap = get_tz_leap(when, &ldb_tai_offset);
break;
case SRC_LIST:
ldb_leap = get_list_leap(when, &ldb_tai_offset);
break;
}
out: out:
*tai_offset = ldb_tai_offset; *tai_offset = ldb_tai_offset;

View file

@ -166,8 +166,8 @@ RCL_AddRefclock(RefclockParameters *params)
if (!inst->driver->init && !inst->driver->poll) if (!inst->driver->init && !inst->driver->poll)
LOG_FATAL("refclock driver %s is not compiled in", params->driver_name); LOG_FATAL("refclock driver %s is not compiled in", params->driver_name);
if (params->tai && !CNF_GetLeapSecTimezone()) if (params->tai && !CNF_GetLeapSecList() && !CNF_GetLeapSecTimezone())
LOG_FATAL("refclock tai option requires leapsectz"); LOG_FATAL("refclock tai option requires leapseclist or leapsectz");
inst->data = NULL; inst->data = NULL;
inst->driver_parameter = Strdup(params->driver_parameter); inst->driver_parameter = Strdup(params->driver_parameter);