/************************************************************************** * * Program 'kernel-time-state' * * Copyright (c) Meinberg Funkuhren, Bad Pyrmont, Germany * * Allows to check or modify the time synchronization state of * kernels that support the adjtimex / ntp_adjtime API calls, * i.e. leap second announcement flags, TAI offset, etc. * * Changing the kernel state usually requires root permissions. * **************************************************************************/ #include #include #include #include #include #include // gmtime, clock_gettime #include // adjtimex or ntp_adjtime #if defined( __linux ) #define kern_api_fnc adjtimex #elif defined( __FreeBSD__ ) \ || defined( __NetBSD__ ) \ || defined( __sun ) #define kern_api_fnc ntp_adjtime #else #error kernel time API not supported #endif static const char program_name[] = "kernel-time-state"; static const char program_version[] = "v1.0"; static const char program_copyright[] = "(c) Meinberg 2019"; static const char program_contact[] = "contact: "; #define N_STATUS_BITS 16 #define XSTRINGIFY(x) #x #define STRINGIFY(x) XSTRINGIFY(x) static int show_current_state; // Show current state. static int show_tai_time; // Show TAI and UTC time. static int quiet; // Quiet, suppress startup message. static int verbose; // Increase verbosity level of output. static int continuous; // Run continuously. static int kern_status_set; // New status flags to be set in the kernel at startup (STA_..). static int kern_status_clear; // New status flags to be cleared in the kernel at startup (STA_..). static int new_tai_offs; // New TAI offset to be passed to the kernel. static int must_set_tai; // Indicate a new TAI offset has to be passed to the kernel. static int must_print_usage; // Must print usage information, then exit. static const char str_read_kernel_time_status[] = "read the kernel time status"; static /*HDR*/ long long delta_ts_ns( const struct timespec *tsp_2, const struct timespec *tsp_1 ) { return ( ( (long long) tsp_2->tv_sec - (long long) tsp_1->tv_sec ) * 1000000000LL ) + ( (long long) tsp_2->tv_nsec - (long long) tsp_1->tv_nsec ); } // delta_ts_ns static /*HDR*/ void print_date_time( const struct timespec *p_ts ) { struct tm *p_tm = gmtime( &p_ts->tv_sec ); if ( p_tm ) printf( "%04u-%02u-%02u %02u:%02u:%02u.%09li", p_tm->tm_year + 1900, p_tm->tm_mon + 1, p_tm->tm_mday, p_tm->tm_hour, p_tm->tm_min, p_tm->tm_sec, p_ts->tv_nsec ); else printf( "(gmtime call failed)" ); } // print_date_time static /*HDR*/ void do_show_tai_utc_time( void ) { struct timespec ts_utc = { 0 }; int rc_utc = clock_gettime( CLOCK_REALTIME, &ts_utc ); int errno_utc = errno; #if defined( CLOCK_TAI ) struct timespec ts_tai = { 0 }; int rc_tai = clock_gettime( CLOCK_TAI, &ts_tai ); int errno_tai = errno; #endif if ( rc_utc == 0 ) { printf( "UTC: " ); print_date_time( &ts_utc ); } else fprintf( stderr, "Failed to read UTC time: %s", strerror( errno_utc ) ); printf( "\n" ); #if defined( CLOCK_TAI ) if ( rc_tai == 0 ) { printf( "TAI: " ); print_date_time( &ts_tai ); if ( rc_utc == 0 ) { char c; double dt = delta_ts_ns( &ts_tai, &ts_utc ) / 1e9; if ( dt < 0 ) { c = '-'; dt = -dt; } else c = '+'; printf( " = UTC %c %.0f s", c, dt ); } } else fprintf( stderr, "Failed to read TAI time: %s", strerror( errno_tai ) ); printf( "\n" ); #else printf( "TAI: not supported by clock_gettime() on this system.\n" ); #endif } // do_show_tai_utc_time static /*HDR*/ const char *kernel_state_string( int rc, int verbose ) { switch ( rc ) { case TIME_OK: return verbose ? ", no leap second warning" : ""; case TIME_INS: return ", < insert leap second warning"; case TIME_DEL: return ", < delete leap second warning"; case TIME_OOP: return ", << leap second in progress"; case TIME_WAIT: return ", < leap second has occured"; case TIME_ERROR: return ", * clock not synchronized"; } // switch return "(** unknown kernel clock state)"; } // kernel_state_string static /*HDR*/ const char *kernel_status_bit_name( int bit_num ) { static const char *bit_names[N_STATUS_BITS] = { "STA_PLL", "STA_PPSFREQ", "STA_PPSTIME", "STA_FLL", "STA_INS", "STA_DEL", "STA_UNSYNC", "STA_FREQHOLD", "STA_PPSSIGNAL", "STA_PPSJITTER", "STA_PPSWANDER", "STA_PPSERROR", "STA_CLOCKERR", "STA_NANO", "STA_MODE", "STA_CLK" }; if ( bit_num < N_STATUS_BITS ) return bit_names[bit_num]; return "(undef)"; } // kernel_status_bit_name static /*HDR*/ int do_kern_api_fnc( struct timex *p, const char *info ) { int rc = kern_api_fnc( p ); if ( rc < 0 ) // error { int this_errno = errno; fprintf( stderr, "*** " STRINGIFY( kern_api_fnc ) " failed" ); if ( info ) fprintf( stderr, " to %s", info ); fprintf( stderr, ": %s\n", strerror( this_errno ) ); } return rc; } // do_kern_api_fnc static /*HDR*/ void show_kern_status( struct timex *p, int rc, int verbose ) { int i; printf( "TAI %i, status 0x%04X:", p->tai, p->status ); for ( i = 0; i < N_STATUS_BITS; i++ ) if ( ( p->status >> i ) & 0x01 ) printf( " %s", kernel_status_bit_name( i ) ); printf( "%s", kernel_state_string( rc, verbose ) ); } // show_kern_status static /*HDR*/ int check_change_kern_status( void ) { struct timex ntv = { 0 }; int rc = 0; if ( kern_status_set || kern_status_clear ) { // First read the current status. memset( &ntv, 0, sizeof( ntv ) ); rc = do_kern_api_fnc( &ntv, str_read_kernel_time_status ); if ( rc < 0 ) // error goto out; ntv.status |= kern_status_set; ntv.status &= ~kern_status_clear; // if ( ntv.status & STA_PLL ) // ntv.status &= ~STA_UNSYNC; ntv.modes |= MOD_STATUS; // TODO or use '=' ? // Write new status flags to the kernel. rc = do_kern_api_fnc( &ntv, "set kernel clock state" ); if ( rc < 0 ) // error { // Not a fatal error. Continue and read the current status. memset( &ntv, 0, sizeof( ntv ) ); rc = do_kern_api_fnc( &ntv, str_read_kernel_time_status ); } show_kern_status( &ntv, rc, 1 ); printf( "\n" ); } if ( must_set_tai ) { memset( &ntv, 0, sizeof( ntv ) ); ntv.modes = MOD_TAI; ntv.constant = new_tai_offs; rc = do_kern_api_fnc( &ntv, "set kernel TAI offset" ); if ( rc < 0 ) // error { // Not a fatal error. Continue and read the current status. memset( &ntv, 0, sizeof( ntv ) ); rc = do_kern_api_fnc( &ntv, str_read_kernel_time_status ); } show_kern_status( &ntv, rc, verbose ); printf( "\n" ); } out: return rc; } // check_change_kern_status static /*HDR*/ int do_test( void ) { const char *err_info = NULL; for (;;) { int rc_1; int rc_2; int rc_a; struct timespec ts_1; struct timespec ts_2; struct timex ntv; struct timespec *tsp; long long dt; memset( &ntv, 0, sizeof( ntv ) ); // Do all 3 calls immediately after each other. rc_1 = clock_gettime( CLOCK_REALTIME, &ts_1 ); rc_a = kern_api_fnc( &ntv ); rc_2 = clock_gettime( CLOCK_REALTIME, &ts_2 ); // check if any call returned an error if ( rc_1 != 0 ) { err_info = "first clock_gettime() call"; break; } if ( rc_a < 0 ) { err_info = STRINGIFY( kern_api_fnc ) "() call"; break; } if ( rc_2 != 0 ) { err_info = "second clock_gettime() call"; break; } tsp = &ts_1; dt = delta_ts_ns( &ts_2, &ts_1 ); printf( "%i ", rc_a ); print_date_time( tsp ); printf( " (%lli.%09li, %lli ns)", (long long) tsp->tv_sec, tsp->tv_nsec, dt ); printf( ", " ); show_kern_status( &ntv, rc_a, verbose ); printf( "\n" ); usleep( 250000 ); } if ( err_info ) { fprintf( stderr, "*** Error: %s failed: %s\n\n", err_info, strerror( errno ) ); return -1; } return 0; } // do_test static /*HDR*/ void check_options( int argc, char *argv[] ) { char c; while ( ( c = getopt( argc, argv, "stT:LidpPmnNuqvch?" ) ) != -1 ) { switch ( c ) { case 's': show_current_state = 1; break; case 't': show_tai_time = 1; break; case 'T': new_tai_offs = atoi( optarg ); must_set_tai = 1; break; case 'L': kern_status_clear |= STA_INS; kern_status_clear |= STA_DEL; break; case 'i': kern_status_set |= STA_INS; kern_status_set &= ~STA_DEL; break; case 'd': kern_status_set |= STA_DEL; kern_status_set &= ~STA_INS; break; case 'p': kern_status_set |= STA_PLL; break; case 'P': kern_status_clear |= STA_PLL; break; case 'm': kern_status_set |= STA_MODE; break; case 'n': kern_status_set |= STA_NANO; break; case 'N': kern_status_clear |= STA_NANO; break; case 'u': kern_status_set |= STA_UNSYNC; kern_status_clear |= STA_PLL | STA_NANO | STA_MODE; break; case 'c': continuous = 1; break; case 'q': quiet = 1; break; case 'v': verbose++; break; case 'h': case '?': default: must_print_usage = 1; } } } // check_options static /*HDR*/ void usage( void ) { printf( "Show or modify the kernel time synchronization state.\n" ); printf( "'root' permissions are required to change the state.\n" ); printf( "\n" ); printf( "Usage: %s [opt] [opt] ...\n", program_name ); printf( "\n" ); printf( "where [opt] can be one or more of the following options:\n" ); printf( "\n" ); printf( " -s Show the current kernel time state\n" ); printf( "\n" ); printf( " -t Read both UTC and TAI time, and show difference\n" ); printf( " -T Set TAI offset to seconds\n" ); printf( " (note the Linux kernel may not accept setting TAI back to 0)\n" ); printf( "\n" ); printf( " -L Clear leap second announcement flags (STA_INS and STA_DEL)\n" ); printf( " -i Set leap second insertion flag (STA_INS)\n" ); printf( " -d Set leap second deletion flag (STA_DEL)\n" ); printf( " (leap second flags may only be accepted at specific dates)\n" ); printf( "\n" ); printf( " -p Set PLL flag (STA_PLL)\n" ); printf( " -P Clear PLL flag (STA_PLL)\n" ); printf( " -m Set mode flag (STA_MODE)\n" ); printf( " -n Set nano time flag (STA_NANO)\n" ); printf( " -N Clear nano time flag (STA_NANO)\n" ); printf( " -u Set \"unsync\" flag (STA_UNSYNC)\n" ); printf( "\n" ); printf( " -q Quiet mode\n" ); printf( " -v Increase verbosity level\n" ); printf( " -c Run continuously, periodically report kernel time status\n" ); printf( "\n" ); printf( " -h, -? Print this usage information\n" ); printf( "\n" ); } // usage int main( int argc, char *argv[] ) { int rc = 0; if ( argc > 1 ) { check_options( argc, argv ); // show_current_state = 1; } else must_print_usage = 1; if ( !quiet || must_print_usage ) fprintf( stderr, "\n%s %s, %s, %s\n", program_name, program_version, program_copyright, program_contact ); if ( must_print_usage ) { usage(); rc = 1; goto out; } if ( show_current_state ) { struct timex ntv = { 0 }; rc = do_kern_api_fnc( &ntv, str_read_kernel_time_status ); if ( rc < 0 ) goto out; show_kern_status( &ntv, rc, verbose ); printf( "\n" ); } if ( show_tai_time ) do_show_tai_utc_time(); rc = check_change_kern_status(); if ( rc < 0 ) goto out; if ( continuous ) rc = do_test(); out: return abs( rc ); }