          <<< EISNER::$2$DIA6:[NOTES$LIBRARY]SITE_MANAGEMENT.NOTE;2 >>>
                              -< Site Management >-
================================================================================
Note 55.31                  NBS Time Synch "on-line"                    31 of 31
EISNER::KOZAM                                       679 lines  28-JUN-1993 18:03
                           -< Minor update to NBS.C >-
--------------------------------------------------------------------------------
    This is an update to NBS.C in the previous notes (someone asked for it,
    so I figured I'd just as well post it).  The only change is a minor
    fix for those magical days when we switch to/from daylight savings
    time.
    -----------------------------------------------------------------------

#include stdio
#include ssdef
#include iodef
#include descrip

#define TRUE	1
#define	FALSE	0

#define	SUCCESS	1
#define	FAILURE	0

#define	TIME_SIZE	51

short	chan;

	/* These variables are set by command line options.  */

	/* Set to TRUE if we are to take daylight savings time into
	account. */
short	set_dst = 0,
	set_precise = 0,
	set_toy = 0,
	check_only = 0;

typedef	long	QUAD[2];

	/* Set to contain the offset required between UTC and local time.
	when daylight savings time is NOT in effect. */
QUAD	offsetbintime,
       	newbintime;
	
struct
	{
	long	header;
	long	terms;
	} mask;

typedef struct	{
	long	mjd;
	short	year,
		month,
		day,
		hour,
		minute,
		second,
		dst,
	     	ls;
	float	dut1,
		msadv;
	char	src[9];
	} TIME_BLOCK;

typedef struct	{
	short	year,
		month,
		day,
		hour,
		minute,
		second,
	  	hundreths;
  	} NUMTIME_BLOCK;

	/* We expect the first argument to be a deltatime used to
	convert between nbs time and local time.  This is typically
	a difference in a number of hours. */
parse_delta(ascii_delta)
char	ascii_delta[];
{
char	asctime[24];
$DESCRIPTOR(asctime_desc, asctime);

	/* Initialize the descriptor to all spaces. */
memset(asctime, ' ', sizeof(asctime));

	/* Copy the offset into asctime so that it is in a descriptor,
	being careful not to overflow it or to copy the NULL.  VMS doesn't
	want to see a null. */
if (strlen(ascii_delta) > 24)
   	strncpy(asctime, ascii_delta, sizeof(asctime));
else
   	strncpy(asctime, ascii_delta, strlen(ascii_delta));

	/* Convert from ascii time format into binary. */
if (SYS$BINTIM(&asctime_desc, offsetbintime) != SS$_NORMAL)
	{
	fprintf(stderr, "Invalid offset time: %s\n", ascii_delta);
	return(FAILURE);
	}

return(SUCCESS);
}

	/* Parse command line options. */
parse_option(option)
char	option[];
{
if (strlen(option) != 2)
	{
	fprintf(stderr, "Invalid option: %s\n", option);
	return(FAILURE);
	}

tolower(option[1]);

switch(option[1])
	{
		/* Set if we are to take daylight savings time into account. */
	case 'd':
		set_dst = TRUE;
		break;

		/* Set if we are not actually going to set the time, only
		set it - useful to see how well your clock is working and
		for non-privileged users to see if the system is on time. */
	case 'c':
		check_only = TRUE;
		break;

		/* Set if we insist on only setting the time if we can be
		very precise, i.e. we have gotten the OTM mark that takes
		the transmission delay into account. */
	case 'p':
		set_precise = TRUE;
		break;

		/* Set if we first want to reset the system time to the
		hardware TOY (time of year) clock inside the VAX.  The
		internal clock is proported to be more accurate than the
		system clock.  */
	case 't':
		set_toy = TRUE;
		break;

	default:
		fprintf(stderr, "Invalid option: %s\n", option);
		return(FAILURE);
		break;
	}

return(SUCCESS);
}

parse_usage(argc, argv)
int	argc;
char	*argv[];
{
short	i;

for(i = 1; i < argc; i++)
	if (argv[i][0] == '-')
		{
		if (!parse_option(argv[i]))
		 	return(FAILURE);
		}
	else
		{
		if (!parse_delta(argv[i]))
		     	return(FAILURE);
		}

return(SUCCESS);
}

open_modem()
{
$DESCRIPTOR(terminal, "MODEM:");

if (sys$assign(&terminal, &chan, 0, 0) != 1)
	{
	fprintf(stderr, "Couldn't assign a channel to the modem.\n");
	exit(-1);
	}

mask.header = 0L;
mask.terms = 0L;

return(SUCCESS);
}

clear_modem()
{
char	otm;

	/* Returns 0 if it timed out while trying to clear the line,
	otherwise, keep reading characters until a '*' (the OTM) is
	received.  */

	/* First do a read with purge in order to clear the line of
	any noise or header characters that may be in the typeahead
	buffer. */
if (readwrite_modem(&otm, sizeof(otm), -10) != 1)
    	return(FAILURE);

while(1)
	{
	if (readwrite_modem(&otm, sizeof(otm), 2) != 1)
	    	return(FAILURE);

	if (otm == '*')
		return(SUCCESS);
	}
}

	/* Readwrite_modem reads characters from the modem, then immediately
	echos them back out.  The argument "timeout" is the number of
	seconds to wait for a character.  If "timeout" is a negative value,
	the line input buffer is purged before reading characters. */
readwrite_modem(buffer, size, timeout)
char	*buffer;
int	size,
	timeout;
{
int	status;
char	iosb[8];
int	received;
int	count;
short	purge;
int	read_flags;

	/* Read characters from the modem line, reading one at a time.
	If the line input buffer is to be purged, do so only before the
	first character.  Doing so before each character is likely to
	cause characters to be lost.

	Keep track of the number of characters read by incrementing "count"
	for each character. */

	/* Set up the options for reading the modem.  */
read_flags = IO$_READVBLK|IO$M_NOFILTR|IO$M_NOECHO;

	/* Include the IO$M_TIMED option if we are using a timeout. */
if (timeout)
	read_flags |= IO$M_TIMED;

	/* If timeout is negative, then we must first purge the line, so
	set the purge to 1.  Also, we must negate timeout because the QIO
	expects a positive number of seconds. */
if (timeout < 0)
	{
	purge = 1;
	timeout = -timeout;
	}
else
	purge = 0;

for(count = 0; count < size; count++)
	{
	/* If the count is 0 (no characters read) and we are supposed to
	purge the line, then also include the IO$M_PURGE option. */

	status = sys$qiow(0, chan,
		read_flags | ((purge && !count) ? IO$M_PURGE : 0),
		iosb, 0, 0, buffer + count, 1, timeout, &mask, 0, 0);

	/* If we got an error, get out of here. */
	if (status != SS$_NORMAL)
		break;

	received = *((short *)(iosb + 2)) + *((short *)(iosb + 6));

	/* Even if we got return status SS$_NORMAL, we may not have received
	any characters - SS$_TIMOUT only seems to be returned if at least
	one character has been read. */
	if (!received)
		break;

	/* Write the character back out to the line.  Don't wait for this
	to complete - we need to overlap reading and writing if we are
	to get everything done in < 1 second at 1200 baud.  */
	status = sys$qio(0, chan, IO$_WRITEVBLK|IO$M_NOFORMAT,
 		0, 0, 0, buffer, 1, 0, 0, 0, 0, 0);

	if (status != SS$_NORMAL)
		break;
	}

if (status != SS$_NORMAL && status != SS$_TIMEOUT)
	{
	fprintf(stderr, "Couldn't get a character from the terminal.\n");
	exit(-1);
	}

if (count)
	return(count);
else
	return(-1);
}

	/* Neither the read_modem nor the write_modem code is used any longer.
	It is included because it may be useful in the future. */

#ifdef UNUSED

read_modem(buffer, size, timeout)
char	*buffer;
int	size,
	timeout;
{
int	status;
char	iosb[8];
int	received;

	/* Gets all characters except ^C,^Q,^S,^Y,^O,^T which,
	as far as I am concerned, VMS can keep! */

if (timeout > 0)
	status = sys$qiow(0, chan,
	       	IO$_READVBLK|IO$M_NOFILTR|IO$M_NOECHO|IO$M_TIMED,
	      	iosb, 0, 0, buffer, size, timeout, &mask, 0, 0);
else if (timeout < 0)
	status = sys$qiow(0, chan,
		IO$_READVBLK|IO$M_NOFILTR|IO$M_NOECHO|IO$M_TIMED|IO$M_PURGE,
	      	iosb, 0, 0, buffer, size, timeout, &mask, 0, 0);
else
	{
	status = sys$qio(0, chan, IO$_READVBLK|IO$M_NOFILTR|IO$M_NOECHO,
		iosb, 0, 0, buffer, size, 0, &mask, 0, 0);

	if (status != SS$_NORMAL)
		fprintf(stderr, "QIO failed: %d\n", status);
	}

if (status != SS$_NORMAL && status != SS$_TIMEOUT)
	{
	fprintf(stderr, "Couldn't get a character from the terminal.\n");
	exit(-1);
	}

received = *((short *)(iosb + 2)) + *((short *)(iosb + 6));

if (received)
	return(received);
else
	return(-1);
}

write_modem(buffer, size)
char	*buffer;
int	size;
{
int	status;

status = sys$qiow(0, chan, IO$_WRITEVBLK|IO$M_NOFORMAT,
	 	0, 0, 0, buffer, size, 0, 0, 0, 0, 0);
}

#endif /* UNUSED */


parse_time(raw, parsed)
char		*raw;
TIME_BLOCK	*parsed;
{
short	count;

	/* Attempt to parse the entire string, even though we discard
	large portions of it.  This is done to ensure that everything
	was received accurately. */
if (sscanf(raw, "%5ld %2hd-%2hd-%2hd %2hd:%2hd:%2hd %2hd %1hd %*c%2f %5f %8s ",
	&(parsed->mjd),
	&(parsed->year), &(parsed->month),  &(parsed->day),
	&(parsed->hour), &(parsed->minute), &(parsed->second),
	&(parsed->dst),  &(parsed->ls),
	&(parsed->dut1), &(parsed->msadv),  &(parsed->src)) != 12)
	return(FAILURE);

return(SUCCESS);
}

char	*months[] = {"JAN", "FEB", "MAR", "APR", "MAY", "JUN",
		     "JUL", "AUG", "SEP", "OCT", "NOV", "DEC"};

show_delta(oldbintime, newbintime)
QUAD		oldbintime,
		newbintime;
{
QUAD		deltabintime;

char   		asctime[24];
NUMTIME_BLOCK	numtime;

$DESCRIPTOR(asctime_desc, asctime);

int	ascii_length;

	/* Display the old time. */
if (SYS$ASCTIM(&ascii_length, &asctime_desc, oldbintime, 0) != SS$_NORMAL)
	{
	fprintf(stderr, "Unable to convert binary to ascii.\n");
	return(FAILURE);
	}

printf("%0.23s ", asctime);

	/* Display the new time. */
if (SYS$ASCTIM(&ascii_length, &asctime_desc, newbintime, 0) != SS$_NORMAL)
	{
	fprintf(stderr, "Unable to convert binary to ascii.\n");
	return(FAILURE);
	}

printf("to %0.23s, ", asctime);

	/* Calculate the time difference. */
qsub(oldbintime[0], oldbintime[1], newbintime[0], newbintime[1], deltabintime);

    	/* Check to see if we moved the clock backwards, in which case
	we need to negate deltabintim.  If we don't do this, the following
	SYS$ASCTIM interprets deltabintim as an absolute time. */
if (deltabintime[1] >= 0)
	qsub(0, 0, deltabintime[0], deltabintime[1], deltabintime);

	/* Display the change in time. */
if (!deltabintime[0] && !deltabintime[1])
	strcpy(asctime, "   0 00:00:00.00");
else if (SYS$ASCTIM(&ascii_length, &asctime_desc, deltabintime, 0)
	!= SS$_NORMAL)
	{
	fprintf(stderr, "Unable to convert binary to ascii.\n");
	return(FAILURE);
	}

printf("delta %0.16s.\n", asctime);

return(SUCCESS);
}

setup_time(parsed)
TIME_BLOCK	*parsed;
{
QUAD  		oldbintime,
   		onehourbintime;
char   		asctime[24];
NUMTIME_BLOCK	numtime;

$DESCRIPTOR(asctime_desc, asctime);

	/* Parse into VAX format the time received from NBS. */
sprintf(asctime, "%02d-%s-19%02d %02d:%02d:%02d.00",
	parsed->day, months[parsed->month - 1], parsed->year,
	parsed->hour, parsed->minute, parsed->second);

	/* Convert from ascii time format into binary. */
if (SYS$BINTIM(&asctime_desc, newbintime) != SS$_NORMAL)
	{
	fprintf(stderr, "Unable to convert ascii to binary.\n");
	return(FAILURE);
	}

	/* Subtract the time offset on the command line from newbintime
	in order to convert it into local time.  Since the deltatime is
	actually a negative number, it is added to the absolute time. */
if (offsetbintime[0] || offsetbintime[1])
	{
	qadd(newbintime[0], newbintime[1], offsetbintime[0], offsetbintime[1],
		newbintime);
	}

if (SYS$NUMTIM(&numtime, newbintime) != SS$_NORMAL)
	{
	fprintf(stderr, "Unable to convert binary to numeric.\n");
	return(FAILURE);
	}

	/* If the daylight savings time switch is ON and daylight savings
	time is currently in effect in the United States, then take this
	into account in making adjustments.  Daylight savings time ADDS
	one hour to the time.

	We must first check if the user wants us to bother with daylight
	savings time in the first place.  Next, we check to see if we
	are in DST, and if so, we do the fixup.  Finally, if we're on
	one of those strange and wonderful days when the time changes
	from ST to DST, we must determine if it is before 2AM and the
	change has not yet gone into effect (so we still need to make
	the correction).  Note that when it is 2AM DST (when we all move
	our clocks back one hour), it is 1AM ST.  If it is after 1AM
	ST (as told to us by the NBS and modified by our local time offset,
	then we are no longer on DST. */

if (set_dst &&
	((parsed->dst >= 2 && parsed->dst <= 50) ||        /* No change day */
		(parsed->dst ==  1 && numtime.hour < 1) || /* ST after 2AM */
		(parsed->dst == 51 && numtime.hour >= 2))) /* DST after 2AM */
		{
			/* Set asctime to a value equal to one hour. */
		strncpy(asctime, "0 01:00:00.00        ", sizeof(asctime));

			/* Convert from ascii time format into binary. */
		if (SYS$BINTIM(&asctime_desc, onehourbintime) != SS$_NORMAL)
			{
			fprintf(stderr, "Unable to convert ascii to binary.\n");
			return(FAILURE);
			}

		qsub(newbintime[0], newbintime[1], onehourbintime[0],
			onehourbintime[1], newbintime);
		}

return(SUCCESS);
}

set_time()
{
QUAD  		oldbintime;

	/* Get the current system time, just so we know where we stand. */
if (SYS$GETTIM(oldbintime) != SS$_NORMAL)
	{
	fprintf(stderr, "Unable to get current system time.\n");
	return(FAILURE);
	}

	/* Actually set the system time clock (but only if we're not
	just checking things out. */
if (!check_only)
	if (SYS$SETIME(newbintime) != SS$_NORMAL)
		{
		fprintf(stderr, "Unable to set new system time.\n");
		return(FAILURE);
		}

	/* Now show how well we did. */
if (!show_delta(oldbintime, newbintime))
	{
	fprintf(stderr, "Unable to display delta time.\n");
	return(FAILURE);
	}

return(SUCCESS);
}

use_toy()
{
QUAD  		oldbintime,
		newbintime;

	/* Get the current system time, just so we know where we stand. */
if (SYS$GETTIM(oldbintime) != SS$_NORMAL)
	{
	fprintf(stderr, "Unable to get current system time.\n");
	return(FAILURE);
	}

	/* Reset the system time according to the time-of-year clock. */
if (SYS$SETIME(0) != SS$_NORMAL)
	{
	fprintf(stderr, "Unable to set new system time using TOY clock.\n");
	return(FAILURE);
	}

	/* Get the new system time, so we can compare. */
if (SYS$GETTIM(newbintime) != SS$_NORMAL)
	{
	fprintf(stderr, "Unable to get new system time.\n");
	return(FAILURE);
	}

	/* Now show how well we did. */
if (!show_delta(oldbintime, newbintime))
	{
	fprintf(stderr, "Unable to display delta time.\n");
	return(FAILURE);
	}

return(SUCCESS);
}

main(argc, argv)
int	argc;
char	*argv[];
{
short		retry;
char		otm;
char		time_line[TIME_SIZE + 1];
TIME_BLOCK	time_block;

time_line[TIME_SIZE] = '\0';

if (!parse_usage(argc, argv))
	{
	fprintf(stderr, "Usage: NBS <deltatime> -d -c -t -p\n");
	exit(-1);
	}

if (set_toy)
	if (use_toy() == SUCCESS)
		exit();
	else
		exit(-1);

	/* Attempt to open a channel to the modem. */
if (!open_modem())
	{
	fprintf(stderr, "Unable to open modem (device MODEM:).\n");
	exit(-1);
	}

	/* We start by clearing the modem, in any garbage has been
	received during the connection process and to get rid of the
	initial greeting from NBS.

	We then read the line of information sent to us (making sure
	that it is of the length we expect), then parse it (making
	sure the it parsed properly, including fields we don't even
	look at), then we set up everything so that the actual time
	setting happens as soon after the OTM mark is received as is
	possible.

	Finally, we wait for the OTM mark to arrive and once verified
	that it is correct, we zip off and set the system time.
	*/

	/* Give us 10 tries to get the time.  If we can't get it in 10
	tries, we probably won't ever get it.  The only time this will
	fail is when we are receiving the time through a packet network,
	in which case both the time line and the OTM may arrive in the
	same packet.  In such cases, we can probably set the time, but
	only roughly (still within a second, though). */
retry = 10;

while(1)
	{
	if (!(retry--))
		{
		fprintf(stderr, "Unable to read time from NBS and set clock.\n");
		exit(-1);
		}

	if (!clear_modem())
		{
		fprintf(stderr, "Unable to clear communication line.\n");
		exit(-1);
		}

	while(1)
		{
		if (readwrite_modem(time_line, sizeof(time_line) - 1, 2) != TIME_SIZE)
			{
	 		fprintf(stderr, "Timed out while attempting to read time.\n");
			break;
			}

		parse_time(time_line, &time_block);

		setup_time(&time_block);

	/* Read with purge - we want to make sure we get the OTM fresh,
	not something that has been sitting in the typeahead buffer
	for a while. */
		if (readwrite_modem(&otm, sizeof(otm), -2) != 1)
			{
	 		fprintf(stderr, "Timed out while attempting to read OTM.\n");
			break;
			}

		if (otm != '*' && otm != '#')
			{
			fprintf(stderr, "Invalid OTM mark received: %d\n",
				otm);
			break;
			}

	/* If the user did not specify to set precisely, we set the
	time no matter what.  If the user has requested precise setting
	only, we set time ONLY if we receive the precise time mark, '#'. */
		if (!set_precise || otm == '#')
		       	if (set_time() == SUCCESS)
				exit();
		}
	}
}
                                                                                                                                                                                                                                                                                                                                                                 