Listing 1: utc.c--Sets the UNIX time from the USNO atomic clock
/*
* utc.c
*
* This program was originally written by Michael Scott Baldwin at
* AT&T Bell Labs and heavily rewritten by Steve Friedl.
*
* This program sets the UNIX time from the US Naval Observatory
* atomic clock in Washington DC. It calls up the observatory at
* 1-202-653-0351 with cu(1), parses the output of the data stream,
* and optionally sets the UNIX system time.
*
* The options are:
* -s specify to set system time via stime() (root only)
* -c sysn specify system name to dial via Systems file
* -D nn specify debug level
* -d don't route cu's stderr to /dev/null (for debug)
*
*/
#include <stdio.h>
#include <ctype.h>
#include <string.h>
#include <time.h>
#include <signal.h>
#include <errno.h>
#include <sys/types.h>
/*
* cu takes one of these two options to enable full debug -- pick one.
*
* SCO UNIX -x9
*/
/*#define CUDEBUG "-d"*/
#define CUDEBUG "-x9"
/*------------------------------------------------------------------------
* definitions for ANSI C.
*/
#ifdef __STDC__
# include <stdlib.h>
# define PROTO(name, args) name args
#else
# define PROTO(name, args) name ( )
# define const /*nothing*/
#endif
/*------------------------------------------------------------------------
* arguments to the exit(2) system call.
*/
#ifndef EXIT_SUCCESS
# define EXIT_SUCCESS 0
# define EXIT_FAILURE 1
#endif
#define TRUE 1
#define FALSE 0
#define pr (void)printf
#define fpr (void)fprintf
#define spr (void)sprintf
#define PLURAL(s) (((s) == 1) ? "" : "s")
#define ABS(a) (((a) < 0) ? (-(a)) : (a))
/*
* "EPOCH" is the number of days since the beginning of Julian time
* (probably around 4517 BC) modulo the number above.
*/
#define EPOCH (2440587%2400000) /* 01/01/1970 */
#define leap(y, m) ((y+m-1 - 70%m) / m) /* also known as 1/1/70 */
#define TONE '*'
#define TIME_FMT "\n%05ld %03d %02d%02d%02d UTC"
static int setflg = 0, /* TRUE = set the time */
cudebug = 0, /* TRUE = enable cu debug */
debuglevel = 0; /* level of debug */
static char const *ProgName, /* program name - argv[0] */
*sysname = 0; /* system name to dial with cu */
static int pgrp; /* current process group */
extern char *optarg;
static PROTO(void set_time, ( void ) );
static PROTO(void show_time, ( void ) );
static PROTO(time_t convert_to_time, ( const char * ) );
static PROTO(char *strip, ( char * ) );
main(argc, argv)
int argc;
char *argv[];
{
int c;
ProgName = argv[0]; /* save command name for err msgs */
while ((c = getopt(argc, argv, "dsc:D:")) != EOF)
{
switch (c)
{
default:
fpr(stderr, "usage: %s [-d] [-s] [-c sysn] [-D lvl]\n",
argv[0]);
exit(EXIT_FAILURE);
case 's': /* -s means set the time */
setflg++;
break;
case 'c': /* -c means call ### via cu */
sysname = optarg;
break;
case 'D':
debuglevel = atoi(optarg);
break;
case 'd': /* -d means debug for cu */
cudebug++;
break;
}
}
if (setflg || sysname)
set_time();
else
show_time();
exit(EXIT_SUCCESS);
}
/*
* This function is called when the alarm times out.
* no input was received from USNO and it is time
* to hang up. kill any cu processes and exit.
*/
static void onsig(signo)
int signo;
{
int rv;
if (sysname)
{
(void) kill(-pgrp, SIGHUP); /* kill children */
(void) wait(&rv); /* wait for the child */
}
fpr(stderr, "%s: nothing from cu\n", ProgName);
exit(EXIT_FAILURE);
}
/*
* Given a system name, execute a cu process to that system and return
* a FILE pointer for that process's standard output. By default,
* any debugging output is rerouted to standard error. If the global
* debugging flags are on, cu information is routed to the terminal.
*
*/
static FILE *cu_open(sysn)
char *sysn;
{
FILE *ifp; /* input FILE pointer (for cu process) */
char cu_cmdbuf[80], /* buffer for the cu command */
phonebuf[80], /* buffer for squeezed phone number */
*p;
/*--------------------------------------------------------
* first, make this current program its own process
* group. cu does not die on SIGPIPE, so
* we have to kill it ourselves. We can't directly get the
* child and child-of-child pids, so we have to kill all
* processes in this pgrp.
*/
pgrp = setpgrp();
(void)signal(SIGHUP, SIG_IGN); /* ignore hangup */
(void)signal(SIGALRM, onsig); /* catch alarm call */
/*--------------------------------------------------------
* Execute the cu program and terminate if not found.
* add the debug option to cu and rely on this
* information being rerouted to /dev/null if the user
* doesn't want to see it.
*/
sprintf(cu_cmdbuf, "cu %s %s %s",
cudebug ? CUDEBUG : "",
sysn,
cudebug ? "" : " 2>/dev/null");
if (debuglevel > 0)
printf("about to popen(%s)\n", cu_cmdbuf);
if (ifp = popen(cu_cmdbuf, "r"), ifp == NULL)
{
fpr(stderr, "%s: cannot open cu\n", ProgName);
exit(EXIT_FAILURE);
}
return(ifp);
}
#define MAX_NTIMES 5
/*
* Fire up cu (or read the standard input). Once a
* connection is made, read lines of input validating for a
* format, and simply ignore invalid input. To prevent the program
* from totally trashing the system time, loop until we receive several
* correct times in a row: "correct" is defined as the previous time
* plus one second. Once we receive MAX_NTIMES (defined just above)
* correct times in a row, we are sure we have it right.
*
* Once a valid time is obtained, either set it as the new system time
* or just print it depending on the user's requests. If time
* is set, report the old and new times to note clock drift.
*/
static void set_time()
{
time_t lasttime = 0;
int ntimes = 0;
int got_valid_time = FALSE;
FILE *ifp;
char ibuf[200]; /* input line buffer */
if (sysname)
ifp = cu_open(sysname);
else
ifp = stdin;
/*----------------------------------------------------------------
* Read lines from USNO. after reading the line,
* parse it in USNO format and ignore lines that don't match.
* activate an alarm so the program doesn't sit
* forever. The popen above returns immedately, so the
* "fgets" is where everything waits. If nothing is received
* for 60 seconds, terminate with an error.
*/
(void)alarm(60); /* set an alarm for one minute */
got_valid_time = FALSE;
while ( !got_valid_time && fgets(ibuf, sizeof ibuf, ifp) )
{
time_t now;
if (debuglevel > 0)
printf("\r\n------> %s\r\n", strip(ibuf));
if ( (now = convert_to_time(ibuf)) < 0 )
{
ntimes = 0;
if (debuglevel > 1)
printf("\r(ignoring line)\n\r");
}
else if (now == 0) /* got the little "mark" */
{
if (ntimes >= MAX_NTIMES)
got_valid_time = TRUE;
}
else if ( ntimes > 0 && (now-1) != lasttime)
{
if (debuglevel > 0)
{
printf("\rgot bad time: %s", ctime(&now) );
printf("\r expect %s", ctime(&lasttime));
printf("\r\n");
}
ntimes = 0;
}
else
{
if (debuglevel > 1)
printf("\rgot time %s\r", ctime(&now));
ntimes++;
lasttime = now;
}
}
(void)alarm(0); /* cancel the alarm */
/*
* All finished, so clean up. If calling with cu,
* kill the child process(es).
*/
if (sysname)
{
(void) kill(-pgrp, SIGHUP); /* kill children */
(void) pclose(ifp);
}
else
(void) fclose(ifp);
/*
* If no valid time was received, defer on all the rest.
*/
if (! got_valid_time)
{
pr("\rCould not get valid time\r\n");
return;
}
/*
* If the system time is actually be set, grab the current time
* and print the old/new time to stdout
*/
if (setflg)
{
int tdiff;
time_t oldtime;
(void) time(&oldtime);
/* if the time did not change, don't do anything. */
if (oldtime == lasttime)
{
pr("System clock is correct!\n");
(void) fputs(ctime(&lasttime), stdout);
return;
}
if (stime(&lasttime) < 0)
{
fpr(stderr, "%s: can't set the time (errno=%d)",
ProgName, errno);
exit(EXIT_FAILURE);
}
pr("Changed time from USNO\r\n");
pr(" was %s\r", ctime(&oldtime));
pr(" now %s\r", ctime(&lasttime));
tdiff = oldtime - lasttime;
pr("\nThe clock was %d second%s %s\n",
ABS(tdiff),
PLURAL(ABS(tdiff)),
(oldtime > lasttime) ? "fast" : "slow");
}
else
(void)fputs(ctime(&lasttime), stdout);
}
/*
* This function prints the time in USNO format for one minute.
*/
static void show_time()
{
int c;
for (c = 0; c < 60; c++)
{
time_t now;
int s, m, h, d, j, y;
/*--------------------------------------------------------
* grab the UNIX time and compute the various pieces in
* USNO format. Note that we do not just take the last
* time and add one second -- it's always better to go right
* to the kernel for the proper time every time.
*/
(void)time(&now); /* get current time */
s = (now % 60); /* seconds */
m = (now /= 60) % 60; /* minutes */
h = (now /= 60) % 24; /* hours */
d = (now /= 24) % 365; /* years */
j = now + EPOCH; /* add in 1/1/1970 */
y = (now /= 365);
d += 1 - leap(y, 4) + leap(y, 100) - leap(y, 400);
(void) putchar(TONE);
(void) printf(TIME_FMT, j, d, h, m, s);
(void) putchar('\n');
(void) fflush(stdout);
(void) sleep(1);
}
}
/*
* Given a line received from the remote end, see if it's a UNIX
* time. We have two types of lines that we get from the USNO for
* each time: the time and a marker. The time is the full details
* of the current minute, second, etc., and the marker says "now".
*
* return either:
*
* 0 marker
* <0 error of some kind
* >0 the UNIX time
*
* The format of a marker line is:
*
* YYYYY DDD HHMMSS UTC
* \ \ \ \ \ \
* \ \ \ \ \ \---- literal "UTC"
* \ \ \ \ \------ second
* \ \ \ \------- minute
* \ \ \-------- hour
* \ \---------- days since start of year
* \------------- Julian date modulo 240000
*/
static time_t convert_to_time(buf)
const char *buf;
{
int rv;
long jyear = 0;
int i, yday, hour, minute, second;
time_t now;
if (buf[0] == '*')
return 0;
if (*buf == '\r')
buf++;
rv = sscanf(buf, "%05ld %03d %02d%02d%02d UTC",
&jyear,
&yday,
&hour,
&minute,
&second);
if (rv != 5)
return -1;
/* calculate the UNIX time and return it */
return (((jyear - EPOCH) * 24 + hour) * 60 + minute) * 60 + second;
}
/*
* strip()
*
* Remove trailing whitespace from the given string.
*/
static char *strip(str)
char *str;
{
char *old = str; /* save ptr to original string */
register char *p = str;
while (*str)
if (!isspace(*str++))
p = str;
*p = '\0';
return old;
}
|