]> git.ipfire.org Git - thirdparty/chrony.git/commitdiff
reference: add new leap second handling modes
authorMiroslav Lichvar <mlichvar@redhat.com>
Tue, 24 Mar 2015 16:29:44 +0000 (17:29 +0100)
committerMiroslav Lichvar <mlichvar@redhat.com>
Fri, 27 Mar 2015 09:37:48 +0000 (10:37 +0100)
In addition to the system driver handling add new modes to slew or step
the system clock for leap second, or ignore it completely. This can be
configured with leapsecmode directive.

conf.c
conf.h
local.c
local.h
reference.c
reference.h

diff --git a/conf.c b/conf.c
index 778c42244b871873c9e672da0d375aaa70058b0c..a07a80ebfc31337d98ff0ac042afae7edc027636 100644 (file)
--- 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 f9b27b12d059ed6e611e6ffd0745de09e69ae736..ab415fa111b6b00d84401e689f9db6c986bd440e 100644 (file)
--- 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 c7b742a0be195ef789686c433f5ec8b3d226989f..8715782a23b639cf9006c54f25ac853063826c1f 100644 (file)
--- 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 f0c351605b97c2428b8ed2651c7a1c4e1e223529..2b17114bafc69845c312fb0b11917b4fcb757557 100644 (file)
--- 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
index e6b4bd5b760d970cd92d08645f6bb481f6e6100b..b5ac33528fa008e830910951b0f918ce6e1ecd6a 100644 (file)
@@ -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);
index 287a4c10e7f5ed39be04b496c1e861c7e15d3973..d7f4874f0cd7b70f9839fec7021a50c52c1ae6d3 100644 (file)
 #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);