diff --git a/conf.c b/conf.c index 778c422..a07a80e 100644 --- a/conf.c +++ b/conf.c @@ -63,6 +63,7 @@ static void parse_deny(char *); static void parse_fallbackdrift(char *); static void parse_include(char *); static void parse_initstepslew(char *); +static void parse_leapsecmode(char *); static void parse_local(char *); static void parse_log(char *); static void parse_mailonchange(char *); @@ -193,6 +194,9 @@ static double tempcomp_T0, tempcomp_k0, tempcomp_k1, tempcomp_k2; static int sched_priority = 0; static int lock_memory = 0; +/* Leap second handling mode */ +static REF_LeapMode leapsec_mode = REF_LeapModeSystem; + /* Name of a system timezone containing leap seconds occuring at midnight */ static char *leapsec_tz = NULL; @@ -440,6 +444,8 @@ CNF_ParseLine(const char *filename, int number, char *line) parse_initstepslew(p); } else if (!strcasecmp(command, "keyfile")) { parse_string(p, &keys_file); + } else if (!strcasecmp(command, "leapsecmode")) { + parse_leapsecmode(p); } else if (!strcasecmp(command, "leapsectz")) { parse_string(p, &leapsec_tz); } else if (!strcasecmp(command, "linux_freq_scale")) { @@ -830,6 +836,23 @@ parse_initstepslew(char *line) /* ================================================== */ +static void +parse_leapsecmode(char *line) +{ + if (!strcasecmp(line, "system")) + leapsec_mode = REF_LeapModeSystem; + else if (!strcasecmp(line, "slew")) + leapsec_mode = REF_LeapModeSlew; + else if (!strcasecmp(line, "step")) + leapsec_mode = REF_LeapModeStep; + else if (!strcasecmp(line, "ignore")) + leapsec_mode = REF_LeapModeIgnore; + else + command_parse_error(); +} + +/* ================================================== */ + static void parse_clientloglimit(char *line) { @@ -1664,6 +1687,14 @@ CNF_GetPidFile(void) /* ================================================== */ +REF_LeapMode +CNF_GetLeapSecMode(void) +{ + return leapsec_mode; +} + +/* ================================================== */ + char * CNF_GetLeapSecTimezone(void) { diff --git a/conf.h b/conf.h index f9b27b1..ab415fa 100644 --- a/conf.h +++ b/conf.h @@ -29,6 +29,7 @@ #define GOT_CONF_H #include "addressing.h" +#include "reference.h" extern void CNF_Initialise(int restarted); extern void CNF_Finalise(void); @@ -75,6 +76,7 @@ extern void CNF_GetBindAddress(int family, IPAddr *addr); extern void CNF_GetBindAcquisitionAddress(int family, IPAddr *addr); extern void CNF_GetBindCommandAddress(int family, IPAddr *addr); extern char *CNF_GetPidFile(void); +extern REF_LeapMode CNF_GetLeapSecMode(void); extern char *CNF_GetLeapSecTimezone(void); /* Value returned in ppm, as read from file */ diff --git a/local.c b/local.c index c7b742a..8715782 100644 --- a/local.c +++ b/local.c @@ -506,6 +506,20 @@ LCL_NotifyExternalTimeStep(struct timeval *raw, struct timeval *cooked, /* ================================================== */ +void +LCL_NotifyLeap(int leap) +{ + struct timeval raw, cooked; + + LCL_ReadRawTime(&raw); + LCL_CookTime(&raw, &cooked, NULL); + + /* Dispatch to all handlers as if the clock was stepped */ + invoke_parameter_change_handlers(&raw, &cooked, 0.0, -leap, LCL_ChangeStep); +} + +/* ================================================== */ + void LCL_AccumulateFrequencyAndOffset(double dfreq, double doffset, double corr_rate) { @@ -599,7 +613,7 @@ LCL_MakeStep(void) /* ================================================== */ void -LCL_SetLeap(int leap) +LCL_SetSystemLeap(int leap) { if (drv_set_leap) { (drv_set_leap)(leap); diff --git a/local.h b/local.h index f0c3516..2b17114 100644 --- a/local.h +++ b/local.h @@ -166,6 +166,10 @@ extern void LCL_ApplyStepOffset(double offset); extern void LCL_NotifyExternalTimeStep(struct timeval *raw, struct timeval *cooked, double offset, double dispersion); +/* Routine to invoke notify handlers on leap second when the system clock + doesn't correct itself */ +extern void LCL_NotifyLeap(int leap); + /* Perform the combination of modifying the frequency and applying a slew, in one easy step */ extern void LCL_AccumulateFrequencyAndOffset(double dfreq, double doffset, double corr_rate); @@ -194,10 +198,10 @@ extern void LCL_Finalise(void); to a timezone problem. */ extern int LCL_MakeStep(void); -/* Routine to schedule a leap second. Leap second will be inserted - at the end of the day if argument is positive, deleted if negative, - and zero cancels scheduled leap second. */ -extern void LCL_SetLeap(int leap); +/* Routine to set the system clock to correct itself for a leap second if + supported. Leap second will be inserted at the end of the day if the + argument is positive, deleted if negative, and zero resets the setting. */ +extern void LCL_SetSystemLeap(int leap); /* Routine to set a frequency correction (in ppm) that should be applied to local clock to compensate for temperature changes. A positive diff --git a/reference.c b/reference.c index e6b4bd5..b5ac335 100644 --- a/reference.c +++ b/reference.c @@ -98,6 +98,17 @@ static double drift_file_age; static void update_drift_file(double, double); +/* Leap second handling mode */ +static REF_LeapMode leap_mode; + +/* Flag indicating the clock was recently corrected for leap second and it may + not have correct time yet (missing 23:59:60 in the UTC time scale) */ +static int leap_in_progress; + +/* Timer for the leap second handler */ +static int leap_timer_running; +static SCH_TimeoutID leap_timeout_id; + /* Name of a system timezone containing leap seconds occuring at midnight */ static char *leap_tzname; static time_t last_tz_leap_check; @@ -136,6 +147,7 @@ static double last_ref_update_interval; /* ================================================== */ static NTP_Leap get_tz_leap(time_t when); +static void update_leap_status(NTP_Leap leap, time_t now, int reset); /* ================================================== */ @@ -148,6 +160,7 @@ handle_slew(struct timeval *raw, void *anything) { double delta; + struct timeval now; if (change_type == LCL_ChangeUnknownStep) { last_ref_update.tv_sec = 0; @@ -155,6 +168,13 @@ handle_slew(struct timeval *raw, } else if (last_ref_update.tv_sec) { UTI_AdjustTimeval(&last_ref_update, cooked, &last_ref_update, &delta, dfreq, doffset); } + + /* When the clock was stepped, check if that doesn't change our leap status + and also reset the leap timeout to undo the shift in the scheduler */ + if (change_type != LCL_ChangeAdjust && our_leap_sec && !leap_in_progress) { + LCL_ReadRawTime(&now); + update_leap_status(our_leap_status, now.tv_sec, 1); + } } /* ================================================== */ @@ -217,6 +237,10 @@ REF_Initialise(void) enable_local_stratum = CNF_AllowLocalReference(&local_stratum); + leap_timer_running = 0; + leap_in_progress = 0; + leap_mode = CNF_GetLeapSecMode(); + leap_tzname = CNF_GetLeapSecTimezone(); if (leap_tzname) { /* Check that the timezone has good data for Jun 30 2008 and Dec 31 2008 */ @@ -263,9 +287,7 @@ REF_Initialise(void) void REF_Finalise(void) { - if (our_leap_sec) { - LCL_SetLeap(0); - } + update_leap_status(LEAP_Unsynchronised, 0, 0); if (drift_file) { update_drift_file(LCL_ReadAbsoluteFrequency(), our_skew); @@ -657,7 +679,72 @@ get_tz_leap(time_t when) /* ================================================== */ static void -update_leap_status(NTP_Leap leap, time_t now) +leap_end_timeout(void *arg) +{ + leap_timer_running = 0; + leap_in_progress = 0; +} + +/* ================================================== */ + +static void +leap_start_timeout(void *arg) +{ + leap_in_progress = 1; + + switch (leap_mode) { + case REF_LeapModeSlew: + LCL_NotifyLeap(our_leap_sec); + LCL_AccumulateOffset(our_leap_sec, 0.0); + LOG(LOGS_WARN, LOGF_Reference, "Adjusting system clock for leap second"); + break; + case REF_LeapModeStep: + LCL_NotifyLeap(our_leap_sec); + LCL_ApplyStepOffset(our_leap_sec); + LOG(LOGS_WARN, LOGF_Reference, "System clock was stepped for leap second"); + break; + case REF_LeapModeIgnore: + LOG(LOGS_WARN, LOGF_Reference, "Ignoring leap second"); + break; + default: + break; + } + + /* Wait until the leap second is over with some extra room to be safe */ + leap_timeout_id = SCH_AddTimeoutByDelay(2.0, leap_end_timeout, NULL); +} + +/* ================================================== */ + +static void +set_leap_timeout(time_t now) +{ + struct timeval when; + + /* Stop old timer if there is one */ + if (leap_timer_running) { + SCH_RemoveTimeout(leap_timeout_id); + leap_timer_running = 0; + leap_in_progress = 0; + } + + if (!our_leap_sec) + return; + + /* Insert leap second at 0:00:00 UTC, delete at 23:59:59 UTC */ + when.tv_sec = (now / (24 * 3600) + 1) * (24 * 3600); + when.tv_usec = 0; + if (our_leap_sec < 0) + when.tv_sec--; + + leap_timeout_id = SCH_AddTimeout(&when, leap_start_timeout, NULL); + leap_timer_running = 1; +} + +/* ================================================== */ + +static void +update_leap_status(NTP_Leap leap, time_t now, int reset) { int leap_sec; @@ -680,9 +767,22 @@ update_leap_status(NTP_Leap leap, time_t now) } } - if (leap_sec != our_leap_sec && !REF_IsLeapSecondClose()) { - LCL_SetLeap(leap_sec); + if (reset || (leap_sec != our_leap_sec && !REF_IsLeapSecondClose())) { our_leap_sec = leap_sec; + + switch (leap_mode) { + case REF_LeapModeSystem: + LCL_SetSystemLeap(our_leap_sec); + break; + case REF_LeapModeSlew: + case REF_LeapModeStep: + case REF_LeapModeIgnore: + set_leap_timeout(now); + break; + default: + assert(0); + break; + } } our_leap_status = leap; @@ -920,7 +1020,7 @@ REF_SetReference(int stratum, our_residual_freq = frequency; } - update_leap_status(leap, raw_now.tv_sec); + update_leap_status(leap, raw_now.tv_sec, 0); maybe_log_offset(our_offset, raw_now.tv_sec); if (step_offset != 0.0) { @@ -1015,7 +1115,7 @@ REF_SetUnsynchronised(void) schedule_fb_drift(&now); } - update_leap_status(LEAP_Unsynchronised, 0); + update_leap_status(LEAP_Unsynchronised, 0, 0); are_we_synchronised = 0; LCL_SetSyncStatus(0, 0.0, 0.0); diff --git a/reference.h b/reference.h index 287a4c1..d7f4874 100644 --- a/reference.h +++ b/reference.h @@ -35,6 +35,14 @@ #include "ntp.h" #include "reports.h" +/* Leap second handling modes */ +typedef enum { + REF_LeapModeSystem, + REF_LeapModeSlew, + REF_LeapModeStep, + REF_LeapModeIgnore, +} REF_LeapMode; + /* Init function */ extern void REF_Initialise(void);