From 24ea4b979a221e8fd944f78857e0b00768adaa78 Mon Sep 17 00:00:00 2001
From: Jean-Claude Bau <jean-claude.bau@cern.ch>
Date: Tue, 25 Jun 2019 16:58:28 +0200
Subject: [PATCH] [New feature] Check for incoming leap second

- Add a new tool (wrs_leapsec) to check for an incoming leap second. It
is done by parsing the leap second file. If a leap second is detected
then adjustment parameters for the clock algoritm are set in the kernel.
- Start the tool at startup
- With cron, execute the tool every 3 hours
- As many tools need to parse the leap seconds file, functions have been
added in the library and then called by these tools
---
 userspace/rootfs_override/etc/cron.d/root     |   2 +
 .../etc/init.d/check_incoming_leap_second.sh  |  46 +++
 .../etc/rcS/S63check_incoming_leap_second.sh  |   1 +
 userspace/tools/.gitignore                    |   1 +
 userspace/tools/Makefile                      |   6 +-
 userspace/tools/time_lib.c                    |  21 +-
 userspace/tools/time_lib.h                    |   4 +-
 userspace/tools/wr_date.c                     |   2 +-
 userspace/tools/wrs_leapsec.c                 | 313 ++++++++++++++++++
 9 files changed, 386 insertions(+), 10 deletions(-)
 create mode 100755 userspace/rootfs_override/etc/init.d/check_incoming_leap_second.sh
 create mode 120000 userspace/rootfs_override/etc/rcS/S63check_incoming_leap_second.sh
 create mode 100644 userspace/tools/wrs_leapsec.c

diff --git a/userspace/rootfs_override/etc/cron.d/root b/userspace/rootfs_override/etc/cron.d/root
index 26fbb1c42..c2dd0793e 100644
--- a/userspace/rootfs_override/etc/cron.d/root
+++ b/userspace/rootfs_override/etc/cron.d/root
@@ -1,3 +1,5 @@
+# run wrs_leapsec every 3 hours to detect incoming leap second
+00 */3 * * * /etc/init.d/check_incoming_leap_second.sh
 # run 1 time per day at 9:10. The script  leap_seconds_file_update.sh is then executed randomly in a 23 hours range 
 10 9   * * * /wr/init.d/leap_seconds_file_update.sh -r 1380
 
diff --git a/userspace/rootfs_override/etc/init.d/check_incoming_leap_second.sh b/userspace/rootfs_override/etc/init.d/check_incoming_leap_second.sh
new file mode 100755
index 000000000..38087f2a5
--- /dev/null
+++ b/userspace/rootfs_override/etc/init.d/check_incoming_leap_second.sh
@@ -0,0 +1,46 @@
+#!/bin/sh
+
+dotconfig=/wr/etc/dot-config
+
+pBaseName=leapsec
+pFullName=wrs_$pBaseName
+process=/wr/bin/$pFullName
+
+echo -n "Starting $process  "
+
+if [ -f $dotconfig ]; then
+	. $dotconfig
+else
+	echo "$0 unable to source dot-config ($dotconfig)!"
+fi
+
+WRS_LOG=$CONFIG_WRS_LOG_OTHER
+
+# if empty turn it to /dev/null
+if [ -z $WRS_LOG ]; then
+	WRS_LOG="/dev/null";
+fi
+# if a pathname, use it
+if echo "$WRS_LOG" | grep / > /dev/null; then
+	eval LOGPIPE=\" \> $WRS_LOG 2\>\&1 \";
+else
+	# not a pathname: use verbatim
+	eval LOGPIPE=\" 2\>\&1 \| logger -t $pBaseName -p $WRS_LOG\"
+fi
+
+# set msg level
+if [ ! -z $CONFIG_WRS_LOG_LEVEL_OTHER ]; then
+	WRS_MSG_LEVEL=$CONFIG_WRS_LOG_LEVEL_OTHER
+	export WRS_MSG_LEVEL
+fi
+
+# be carefull with pidof, no running script should have the same name as
+# process
+if pidof $pFullName  > /dev/null; then
+	# Process s already running
+	eval echo "Failed (already running?)" $LOGPIPE
+else
+	eval $process $LOGPIPE \&
+	echo "OK"
+fi
+
diff --git a/userspace/rootfs_override/etc/rcS/S63check_incoming_leap_second.sh b/userspace/rootfs_override/etc/rcS/S63check_incoming_leap_second.sh
new file mode 120000
index 000000000..22d270a1a
--- /dev/null
+++ b/userspace/rootfs_override/etc/rcS/S63check_incoming_leap_second.sh
@@ -0,0 +1 @@
+../init.d/check_incoming_leap_second.sh
\ No newline at end of file
diff --git a/userspace/tools/.gitignore b/userspace/tools/.gitignore
index d395e80c5..2c797b7a4 100644
--- a/userspace/tools/.gitignore
+++ b/userspace/tools/.gitignore
@@ -26,3 +26,4 @@ wrs_sfp_dump
 wrs_pps_control
 wrs_throttling
 utc_leap_test
+/wrs_leapsec
diff --git a/userspace/tools/Makefile b/userspace/tools/Makefile
index f3e936348..98d1ff9f5 100644
--- a/userspace/tools/Makefile
+++ b/userspace/tools/Makefile
@@ -4,7 +4,7 @@
 
 TOOLS = rtu_stat wr_mon wr_phytool wrs_pps_control spll_dbg_proxy load-lm32 load-virtex com
 TOOLS += mapper wmapper
-TOOLS += wrs_version wr_date lm32-vuart wrs_pstats
+TOOLS += wrs_version wr_date lm32-vuart wrs_pstats wrs_leapsec
 TOOLS += wrs_vlans wrs_dump_shmem
 TOOLS += sdb-read
 TOOLS += nbtee
@@ -88,6 +88,10 @@ wr_mon: wr_mon.o term.o  time_lib.o
 wr_date: wr_date.o time_lib.o
 	${CC} -o $@ $^ $(LDFLAGS)
 
+wrs_leapsec: wrs_leapsec.o time_lib.o
+	${CC} -o $@ $^ $(LDFLAGS)
+
+
 fix_tai_offset: fix_tai_offset.o time_lib.o
 	${CC} -o $@ $^ $(LDFLAGS)
 
diff --git a/userspace/tools/time_lib.c b/userspace/tools/time_lib.c
index 8ecbb4e11..23550dd41 100644
--- a/userspace/tools/time_lib.c
+++ b/userspace/tools/time_lib.c
@@ -154,22 +154,25 @@ int timeval_subtract(struct timeval *result, struct timeval *x, struct timeval *
 	return (result->tv_sec < 0) || (result->tv_usec<0);
 }
 
+
 /**
  * Get the TAI offset decoding the leap seconds file
  * @param leapSecondsFile The leapSecond file name. Use the default one if NULL
  * @param utc             Number of seconds since the Epoch, 1970-01-01 00:00:00 +0000 (UTC)
+ * @param nextTai         if nextTai is not NULL, set to the next TAI in the next 12 hours
  * @param hasExpired      if hasExpired is not NULL, set to 1 if the file has expired
  * @return >=0 : TAI offset
  *          -1 : Error
  */
 
 #define OFFSET_NTP_TIME_TO_UTC ((uint64_t)2208988800LL) /* NTP time to UTC (1900 to 1970 in seconds) */
+#define TWELVE_HOURS           ((uint64_t)(60*60*12) )  /* Number of seconds in 12 hours */
 
 static char *defaultLeapSecondsFile = "/etc/leap-seconds.list";
 
-int getTaiOffsetFromLeapSecondsFile(char *leapSecondsFile, time_t utc, int *hasExpired) {
+int getTaiOffsetFromLeapSecondsFile(char *leapSecondsFile, time_t utc, int *nextTai,int *hasExpired ) {
 
-	uint64_t expirationDate, ntpTime ;
+	uint64_t expirationDate, ntpTime,nextNtpTime;
 	int tai_offset = 9; /* For the time before 1972, we consider that the offset is 9 */
 	FILE *f;
 	char line[128];
@@ -178,6 +181,7 @@ int getTaiOffsetFromLeapSecondsFile(char *leapSecondsFile, time_t utc, int *hasE
 		leapSecondsFile=defaultLeapSecondsFile;
 
 	ntpTime = (uint64_t)utc + OFFSET_NTP_TIME_TO_UTC;
+	nextNtpTime= ntpTime + TWELVE_HOURS;
 
 	f = fopen(leapSecondsFile, "r");
 	if (!f) {
@@ -200,10 +204,16 @@ int getTaiOffsetFromLeapSecondsFile(char *leapSecondsFile, time_t utc, int *hasE
 			continue;
 
 		/* check this line, and apply it if it's in the past */
-		if (leapNtpTime < ntpTime)
+		if (leapNtpTime < ntpTime) {
 			tai_offset = tai;
-		else if (leapNtpTime > ntpTime)
+			if ( nextTai )
+				*nextTai=tai;
+		}
+		else {
+			if ( nextTai && leapNtpTime<=nextNtpTime )
+				*nextTai=tai;
 			break; // File read can be aborted
+		}
 	}
 	fclose(f);
 	return tai_offset;
@@ -232,7 +242,7 @@ int fixHostTai(char *leapSecondsFile, time_t utc, int *hasExpired, int verbose)
 		return 0;
 	}
 
-	if ( (tai_offset=getTaiOffsetFromLeapSecondsFile(NULL,utc,hasExpired))<0 ) {
+	if ( (tai_offset=getTaiOffsetFromLeapSecondsFile(NULL,utc,NULL,hasExpired))<0 ) {
 		fprintf(stderr, "%s: Cannot get TAI offset\n", __func__);
 		return 0;
 	}
@@ -263,4 +273,3 @@ int fixHostTai(char *leapSecondsFile, time_t utc, int *hasExpired, int verbose)
 		printf("Current TAI offset: %i\n", t.tai);
 	return tai_offset;
 }
-
diff --git a/userspace/tools/time_lib.h b/userspace/tools/time_lib.h
index fc4090ac4..ef7dd7145 100644
--- a/userspace/tools/time_lib.h
+++ b/userspace/tools/time_lib.h
@@ -31,6 +31,6 @@ extern char * timeToString(struct pp_time *time,char *buf);
 extern char * timestampToString(struct Timestamp *time,char *buf);
 extern char * relativeDifferenceToString(RelativeDifference time, char *buf );
 extern int timeval_subtract(struct timeval *result, struct timeval *x, struct timeval *y);
-extern int getTaiOffsetFromLeapSecondsFile(char *leapSecondsFile, time_t utc, int *hasExpired);
+extern int getTaiOffsetFromLeapSecondsFile(char *leapSecondsFile, time_t utc, int *nextTai,int *hasExpired );
 extern int fixHostTai(char *leapSecondsFile, time_t utc, int *hasExpired, int verbose);
-;
+
diff --git a/userspace/tools/wr_date.c b/userspace/tools/wr_date.c
index 816f79569..06ac82774 100644
--- a/userspace/tools/wr_date.c
+++ b/userspace/tools/wr_date.c
@@ -407,7 +407,7 @@ int wrdate_internal_set(volatile struct PPSG_WB *pps, int taiOnly, int adjSecOnl
 			return 0;
 		}
 		printf("Current TAI offset= %d\n",t.tai);
-		if ( (taiOffset=getTaiOffsetFromLeapSecondsFile(NULL,time(NULL),&hasExpired))==-1) {
+		if ( (taiOffset=getTaiOffsetFromLeapSecondsFile(NULL,time(NULL),NULL,&hasExpired))==-1) {
 			fprintf(stderr, "%s: Cannot fix read TAI offset from leap seconds file\n" ,prgname);
 			return 0;
 		}
diff --git a/userspace/tools/wrs_leapsec.c b/userspace/tools/wrs_leapsec.c
new file mode 100644
index 000000000..c64d2a7fb
--- /dev/null
+++ b/userspace/tools/wrs_leapsec.c
@@ -0,0 +1,313 @@
+/*
+ * wrs_leapsec.c
+ *
+ * This tools read the local time and checks for a incoming leap second.
+ * If it detects a incoming leap seconds in the next 12 hours, the information
+ * is set in the kernel and will be available for PPSi.
+ *
+ *  Created on: Jun 18, 2019
+ *  Authors:
+ * 		- Jean-Claude BAU / CERN
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License...
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <getopt.h>
+#include <time.h>
+#include <unistd.h>
+#include <sys/timex.h>
+#include <errno.h>
+#include <libwr/wrs-msg.h>
+#include <libwr/softpll_export.h>
+#include <rt_ipc.h>
+#include "time_lib.h"
+
+/* STATUS reported to SNMP */
+/* Ordered by priority (low to high) */
+
+typedef enum {
+	MSG_NO_ERROR,
+	MSG_LEAP_SECOND_FILE_EXPIRED,
+	MSG_ERROR,
+	MSG_TAI_READ_ERROR,
+	MSG_LEAP_SECOND_WILL_BE_INSERTED,
+	MSG_LEAP_SECOND_WILL_BE_DELETED,
+} check_status_e;
+
+typedef struct {
+	check_status_e statusId;
+	char * msg;
+}statusMsg_t;
+
+static statusMsg_t statusMsg[] = {
+		{ MSG_NO_ERROR, "no_changes"},
+		{ MSG_LEAP_SECOND_FILE_EXPIRED,"leap_sec_file_expired"},
+		{ MSG_ERROR, "error_detected"},
+		{ MSG_TAI_READ_ERROR, "tai_read_error"},
+		{ MSG_LEAP_SECOND_WILL_BE_INSERTED, "leap_sec_inserted"},
+		{ MSG_LEAP_SECOND_WILL_BE_DELETED, "leap_sec_deleted"},
+};
+
+static check_status_e status=MSG_NO_ERROR;
+static char *leapFileName=NULL;
+static int opt_verbose=0;
+static int opt_quiet=0;
+static int opt_clear=0;
+static int opt_dryrun=0;
+static int opt_test=0;
+static int leapSecondMask=0; /* 1 or -1 */
+static char *prgName;
+
+
+// Log messages : for verbose mode , do not log messages but just print them on screen
+#define pr_error(...)	wrs_msg(LOG_ERR, __VA_ARGS__)
+#define pr_err(...)	wrs_msg(LOG_ERR, __VA_ARGS__)
+#define pr_warning(...)	wrs_msg(LOG_WARNING, __VA_ARGS__)
+#define pr_warn(...)	wrs_msg(LOG_WARNING, __VA_ARGS__)
+#define pr_info(...)	wrs_msg(LOG_INFO, __VA_ARGS__)
+#define pr_debug(...)	wrs_msg(LOG_DEBUG, __VA_ARGS__)
+
+#define STATUS_FILE "/tmp/leapseconds_check_status"
+
+
+static inline void setStatus(check_status_e newStatus) {
+	if ( newStatus>status ) {
+		status=newStatus;
+	}
+}
+
+static char* getStatusAsString(check_status_e st) {
+
+	int i;
+
+	for (i=0; i<(sizeof(statusMsg)/sizeof(statusMsg_t)); i++ )
+		if (statusMsg[i].statusId==st) {
+			return statusMsg[i].msg;
+		}
+	return getStatusAsString(MSG_ERROR);
+}
+
+static void saveStatus(void) {
+	char *msg=getStatusAsString(status);
+
+	FILE *fd=fopen(STATUS_FILE,"w");
+
+	if ( fd==NULL ) {
+		pr_error("Cannot open file %s\n", STATUS_FILE);
+		return;
+	}
+	fprintf(fd,"%s\n",msg);
+	fclose(fd);
+}
+
+static void show_help(void)
+{
+	printf("WR tool used to update the leap second from leap seconds file.\n"
+			"Usage: wrs_leapsec [options], where [options] can be:\n"
+			"\t-f leap_second_file  If not set, use the default one\n"
+			"\t-n                   Dry run - No actions\n"
+			"\t-l value             Testing mode. Set the kernel for an incoming leap second\n"
+			"\t   					'value' must be 1 (leap61) or -1 (leap59)\n"
+			"\t-h                   display help\n"
+			"\t-v                   set verbosity on\n"
+			"\t-q                   Quiet mode. Display only important messages\n"
+			"\t-c                   Clear leap second in the kernel\n"
+			"\n");
+}
+
+static void lsec_parse_cmdline(int argc, char *argv[])
+{
+	int opt;
+
+	while ((opt = getopt(argc, argv, "cqnhvl:f:")) != -1) {
+		switch (opt) {
+
+		case 'h':
+			show_help();
+			exit(0);
+			break;
+
+		case 'f':
+			leapFileName = optarg;
+			break;
+
+		case 'l':
+			if ( sscanf(optarg,"%d",&leapSecondMask )==1 ) {
+				leapSecondMask= leapSecondMask>=1 ? STA_INS : STA_DEL;
+				opt_test=1;
+			} else {
+				fprintf(stderr,"%s: Invalid parameter to -l option.\n",prgName);
+				show_help();
+				exit(-1);
+			}
+			break;
+
+
+		case 'n':
+			opt_dryrun=1;
+			break;
+
+		case 'c':
+			opt_clear=1;
+			break;
+
+		case 'v':
+			opt_verbose=1;
+			break;
+
+		case 'q':
+			opt_quiet=1;
+			break;
+
+		default:
+			fprintf(stderr,"Unrecognized option. Call %s -h for help.\n",prgName);
+			break;
+		}
+	}
+
+	if ( opt_verbose && opt_quiet )
+		opt_verbose=0; /* Not compatible */
+	if ( opt_dryrun && opt_clear )
+		opt_clear=0; /* Not compatible */
+
+}
+
+int getTimingMode(void) {
+	static int connected=FALSE;
+	struct rts_pll_state pstate;
+
+	if ( !connected ) {
+		if(	rts_connect(NULL) < 0)
+			return -1;
+		connected=TRUE;
+	}
+	if (rts_get_state(&pstate)<0 )
+		return -1;
+	return pstate.mode;
+}
+
+static inline int adjustTimeX(struct timex *t) {
+	int ret;
+
+	if ((ret=adjtimex(t)) == -1) {
+		pr_error("Fatal: adjtimex(): %s\n",strerror(errno));
+		setStatus(MSG_ERROR);
+	}
+	return ret;
+}
+
+int applyToKernel(void ) {
+	struct timex t;
+	int mask=leapSecondMask;
+	int leapSecEnabled=0;
+
+	/* Read the kernel information */
+	memset(&t, 0, sizeof(t));
+	if (adjustTimeX(&t) == -1)
+		return -1;
+
+	if ( (t.status & STA_INS)!=0 ) {
+		if ( opt_verbose )
+			printf("%s: A leap second will be inserted tonight.\n",prgName);
+		mask=0;
+		leapSecEnabled=1;
+		setStatus(MSG_LEAP_SECOND_WILL_BE_INSERTED);
+	}
+	if ( (t.status & STA_DEL)!=0 ) {
+		if ( opt_verbose )
+			printf("%s: A leap second will be removed tonight.\n",prgName);
+		mask=0;
+		leapSecEnabled=1;
+		setStatus(MSG_LEAP_SECOND_WILL_BE_DELETED);
+	}
+
+	// opt_clear and opt_dryrun never set at the same time
+	if ( opt_clear && leapSecEnabled ) {
+		if ( opt_verbose )
+			printf("%s: Clearing leap second in the kernel.\n",prgName);
+		t.modes=ADJ_STATUS;
+		t.status&=~(STA_INS|STA_DEL);
+		if (adjustTimeX(&t) == -1)
+			return -1;
+	}
+
+	if ( !opt_dryrun && mask !=0 ) {
+		if ( mask == STA_INS ) {
+			pr_info("Leap second inserted. Will be applied after the last second of the UTC day\n");
+			setStatus(MSG_LEAP_SECOND_WILL_BE_INSERTED);
+		}
+		if ( mask == STA_DEL ) {
+			pr_info("Leap second deleted. Will be applied after the last second of the UTC day\n");
+			setStatus(MSG_LEAP_SECOND_WILL_BE_DELETED);
+		}
+		memset(&t, 0, sizeof(t));
+		t.modes=ADJ_STATUS;
+		t.status|=mask;
+		if (adjustTimeX(&t) == -1)
+			return -1;
+	}
+	return 1;
+}
+
+int main(int argc, char *argv[])
+{
+	int ret;
+
+	wrs_msg_init(argc,argv);
+
+	prgName=argv[0];
+
+	lsec_parse_cmdline(argc,argv);
+
+	pr_info("Commit %s, built on " __DATE__ "\n",__GIT_VER__);
+
+	if ( ! opt_test ) {
+		int taiOffset, nextTaiOffset, hasExpired;
+		int tm=getTimingMode();
+
+		switch (tm) {
+		case SPLL_MODE_GRAND_MASTER:
+		case SPLL_MODE_FREE_RUNNING_MASTER :
+			if ( (taiOffset=getTaiOffsetFromLeapSecondsFile(leapFileName,time(NULL),&nextTaiOffset,&hasExpired))==-1) {
+				pr_error("Fatal: Cannot read TAI offset from leap seconds file\n");
+				setStatus(MSG_TAI_READ_ERROR);
+				ret=-1;
+				goto error;
+			}
+			if ( hasExpired )
+				setStatus(MSG_LEAP_SECOND_FILE_EXPIRED);
+			if ( nextTaiOffset > taiOffset ) {
+				leapSecondMask=STA_INS;
+			}
+			if ( nextTaiOffset < taiOffset ) {
+				leapSecondMask=STA_DEL;
+			}
+			if ( opt_verbose && hasExpired ) {
+				printf("%s: The leap seconds file has expired !!!\n", prgName);
+			}
+			break;
+		default:
+			if ( opt_verbose )
+				printf("%s: Leap second can only by applied when timing mode is GM or FR\n",prgName);
+		}
+	}
+
+	ret=applyToKernel();
+
+	error:;
+	saveStatus();
+	return ret;
+}
-- 
GitLab