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