Commit 24ea4b97 authored by Jean-Claude BAU's avatar Jean-Claude BAU Committed by Maciej Lipinski

[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
parent be91ae48
# 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 # 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 10 9 * * * /wr/init.d/leap_seconds_file_update.sh -r 1380
#!/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
../init.d/check_incoming_leap_second.sh
\ No newline at end of file
...@@ -26,3 +26,4 @@ wrs_sfp_dump ...@@ -26,3 +26,4 @@ wrs_sfp_dump
wrs_pps_control wrs_pps_control
wrs_throttling wrs_throttling
utc_leap_test utc_leap_test
/wrs_leapsec
...@@ -4,7 +4,7 @@ ...@@ -4,7 +4,7 @@
TOOLS = rtu_stat wr_mon wr_phytool wrs_pps_control spll_dbg_proxy load-lm32 load-virtex com TOOLS = rtu_stat wr_mon wr_phytool wrs_pps_control spll_dbg_proxy load-lm32 load-virtex com
TOOLS += mapper wmapper 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 += wrs_vlans wrs_dump_shmem
TOOLS += sdb-read TOOLS += sdb-read
TOOLS += nbtee TOOLS += nbtee
...@@ -88,6 +88,10 @@ wr_mon: wr_mon.o term.o time_lib.o ...@@ -88,6 +88,10 @@ wr_mon: wr_mon.o term.o time_lib.o
wr_date: wr_date.o time_lib.o wr_date: wr_date.o time_lib.o
${CC} -o $@ $^ $(LDFLAGS) ${CC} -o $@ $^ $(LDFLAGS)
wrs_leapsec: wrs_leapsec.o time_lib.o
${CC} -o $@ $^ $(LDFLAGS)
fix_tai_offset: fix_tai_offset.o time_lib.o fix_tai_offset: fix_tai_offset.o time_lib.o
${CC} -o $@ $^ $(LDFLAGS) ${CC} -o $@ $^ $(LDFLAGS)
......
...@@ -154,22 +154,25 @@ int timeval_subtract(struct timeval *result, struct timeval *x, struct timeval * ...@@ -154,22 +154,25 @@ int timeval_subtract(struct timeval *result, struct timeval *x, struct timeval *
return (result->tv_sec < 0) || (result->tv_usec<0); return (result->tv_sec < 0) || (result->tv_usec<0);
} }
/** /**
* Get the TAI offset decoding the leap seconds file * Get the TAI offset decoding the leap seconds file
* @param leapSecondsFile The leapSecond file name. Use the default one if NULL * @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 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 * @param hasExpired if hasExpired is not NULL, set to 1 if the file has expired
* @return >=0 : TAI offset * @return >=0 : TAI offset
* -1 : Error * -1 : Error
*/ */
#define OFFSET_NTP_TIME_TO_UTC ((uint64_t)2208988800LL) /* NTP time to UTC (1900 to 1970 in seconds) */ #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"; 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 */ int tai_offset = 9; /* For the time before 1972, we consider that the offset is 9 */
FILE *f; FILE *f;
char line[128]; char line[128];
...@@ -178,6 +181,7 @@ int getTaiOffsetFromLeapSecondsFile(char *leapSecondsFile, time_t utc, int *hasE ...@@ -178,6 +181,7 @@ int getTaiOffsetFromLeapSecondsFile(char *leapSecondsFile, time_t utc, int *hasE
leapSecondsFile=defaultLeapSecondsFile; leapSecondsFile=defaultLeapSecondsFile;
ntpTime = (uint64_t)utc + OFFSET_NTP_TIME_TO_UTC; ntpTime = (uint64_t)utc + OFFSET_NTP_TIME_TO_UTC;
nextNtpTime= ntpTime + TWELVE_HOURS;
f = fopen(leapSecondsFile, "r"); f = fopen(leapSecondsFile, "r");
if (!f) { if (!f) {
...@@ -200,10 +204,16 @@ int getTaiOffsetFromLeapSecondsFile(char *leapSecondsFile, time_t utc, int *hasE ...@@ -200,10 +204,16 @@ int getTaiOffsetFromLeapSecondsFile(char *leapSecondsFile, time_t utc, int *hasE
continue; continue;
/* check this line, and apply it if it's in the past */ /* check this line, and apply it if it's in the past */
if (leapNtpTime < ntpTime) if (leapNtpTime < ntpTime) {
tai_offset = tai; tai_offset = tai;
else if (leapNtpTime > ntpTime) if ( nextTai )
*nextTai=tai;
}
else {
if ( nextTai && leapNtpTime<=nextNtpTime )
*nextTai=tai;
break; // File read can be aborted break; // File read can be aborted
}
} }
fclose(f); fclose(f);
return tai_offset; return tai_offset;
...@@ -232,7 +242,7 @@ int fixHostTai(char *leapSecondsFile, time_t utc, int *hasExpired, int verbose) ...@@ -232,7 +242,7 @@ int fixHostTai(char *leapSecondsFile, time_t utc, int *hasExpired, int verbose)
return 0; 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__); fprintf(stderr, "%s: Cannot get TAI offset\n", __func__);
return 0; return 0;
} }
...@@ -263,4 +273,3 @@ int fixHostTai(char *leapSecondsFile, time_t utc, int *hasExpired, int verbose) ...@@ -263,4 +273,3 @@ int fixHostTai(char *leapSecondsFile, time_t utc, int *hasExpired, int verbose)
printf("Current TAI offset: %i\n", t.tai); printf("Current TAI offset: %i\n", t.tai);
return tai_offset; return tai_offset;
} }
...@@ -31,6 +31,6 @@ extern char * timeToString(struct pp_time *time,char *buf); ...@@ -31,6 +31,6 @@ extern char * timeToString(struct pp_time *time,char *buf);
extern char * timestampToString(struct Timestamp *time,char *buf); extern char * timestampToString(struct Timestamp *time,char *buf);
extern char * relativeDifferenceToString(RelativeDifference 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 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); extern int fixHostTai(char *leapSecondsFile, time_t utc, int *hasExpired, int verbose);
;
...@@ -407,7 +407,7 @@ int wrdate_internal_set(volatile struct PPSG_WB *pps, int taiOnly, int adjSecOnl ...@@ -407,7 +407,7 @@ int wrdate_internal_set(volatile struct PPSG_WB *pps, int taiOnly, int adjSecOnl
return 0; return 0;
} }
printf("Current TAI offset= %d\n",t.tai); 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); fprintf(stderr, "%s: Cannot fix read TAI offset from leap seconds file\n" ,prgname);
return 0; return 0;
} }
......
/*
* 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;
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment