Cover V04, I04
Article
Figure 1
Listing 1
Sidebar 1

jul95.tar


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;
}