home *** CD-ROM | disk | FTP | other *** search
- /*
- * gc1000.c
- *
- * This program will connect to the Hath/Zenith GC-1000
- * "Most Accurate Clock" to collect the date and time.
- *
- * The assumptions are that some form of System V is being
- * used or that there is a kernel variable named "one_sec"
- * that counts down from HZ to zero which is when the
- * time of day clock changes to the next second (and one_sec
- * is reloaded with HZ.
- *
- * It is further assumed that the GC-1000 clock is set for
- * 9600bps (inducing a 25ms delay) and NOT in auto-mode, i.e.
- * a time line is sent when RTS (pin 5) goes high. This
- * should be wired to DTR since it goes high when the port
- * is opened. Note that the GC-1000 waits one second after
- * the RTS (DTR) transition before it sends a time line.
- *
- * Copyright (copr) 1991, William L. Kennedy Jr. You may do
- * anything with this intellectual property other than claim
- * that you wrote it. There is no expressed or implied
- * warranty, if you break it or it breaks something of yours,
- * that's just too bad.
- */
- #include <sys/types.h>
- #include <sys/param.h>
- #include <stdio.h>
- #include <termio.h>
- #include <fcntl.h>
- #include <setjmp.h>
- #include <signal.h>
- #include <time.h>
- #include <nlist.h>
-
- /*
- * This is the structure which gets filled in
- * with the ASCII data received from the GC-1000
- */
- struct rs232 {
- char hour[2];
- char colon_1;
- char minute[2];
- char colon_2;
- char second[2];
- char point;
- char tenths;
- char spaces[5];
- char month[2];
- char slash_1;
- char day[2];
- char slash_2;
- char year[2];
- char cr;
- char eos;
- };
-
- int wwv_fd, mem_fd, debug;
- jmp_buf timed_out;
-
- int time_out();
-
- /*
- * The termio structure might need some adjusting to fit
- * various hardware but this (and the and 0x7f on each
- * character read) seems to fit every place I've tried it.
- */
- struct termio wwv_tty = {
- 0,
- ONLCR,
- B9600 | CREAD | CS8 | HUPCL,
- 0,
- 0,
- { 0377, 0377, 0377, 0377, 0, 0, 0, 0 }
- };
-
- /*
- * This structure is used to get the location of the kernel
- * variable one_sec. When one_sec goes to zero (or negative)
- * the time of day value (what's returned by time(2) ) gets
- * incremented and one_sec is reloaded with HZ. The hardware
- * timer interrupt decrements one_sec. Note that this value
- * is rewritten, as HZ, to /dev/kmem when root is setting the
- * time to agree with the GC-1000.
- */
- struct nlist nl[] = {
- { "one_sec" },
- { (char *) 0 }
- };
-
- /*
- * The DEF_TTY is what is used if no -t argument is
- * provided on the command line. The DEF_KMEM and
- * DEF_UNIX defaults should not need changing but you
- * can point at something else with the -k and -u
- * command line arguments.
- */
- #define DEF_TTY "/dev/tty05"
- #define DEF_KMEM "/dev/kmem"
- #define DEF_UNIX "/unix"
- #define MAX_TRIES 3 /* Number of read failures to tolerate */
- #define MAX_DIFF 45L /* Maximum seconds in error to tolerate */
-
- main ( argc, argv )
- int argc;
- char **argv;
- {
-
- extern int optind;
- extern char *optarg;
- long sys_time, time(), wwv_time, now_time, diff_secs;
- int ret, set_time, kmem_mode, one_sec, tries;
- char time_buf[32], tty_name[32], kmem_name[32], unix_name[32];
- char *t;
- struct rs232 char_time, *wwv_buf;
- struct tm *time_now, *localtime();
-
- strcpy(tty_name,DEF_TTY);
- strcpy(kmem_name,DEF_KMEM);
- strcpy(unix_name,DEF_UNIX);
- wwv_buf = (struct rs232 *) &char_time.hour[0];
- mem_fd = tries = set_time = debug = 0;
-
- /*
- * Process the command line arguments. Note that if the
- * program is run where stdout is not a tty (like out of
- * cron), the debug display is disabled even if you enable
- * it on the command line.
- */
- while ( -1 != (ret = getopt(argc,argv,"t:k:u:sx")) )
- switch ( ret ) {
-
- case 'k':
- strcpy(kmem_name,optarg);
- break;
-
- case 's':
- set_time++;
- break;
-
- case 't':
- strcpy(tty_name,optarg);
- break;
-
- case 'u':
- strcpy(unix_name,optarg);
- break;
-
- case 'x':
- debug++;
- break;
-
- case '?':
- printf("Usage: %s -t tty -k kmem -u unix -s\n");
- exit(0);
- }
-
- /*
- * If isatty(1) is false and debugging has been set,
- * defeat debugging.
- */
- if ( ! isatty(1) && debug )
- debug--;
-
- if ( debug ) {
- one_sec = HZ;
- printf("tty=%s, kmem=%s, unix=%s, set time=%c HZ=%d\n",
- tty_name,kmem_name,unix_name,
- (set_time) ? 'y' : 'n',one_sec );
- }
-
- /*
- * Now try and open kernel memory. If we're
- * superuser, we're eligible to write, else
- * read only. We'll use this to determine the
- * count in the one_sec variable, i.e. the
- * number of HZ ticks until the next even second.
- *
- * Note that this isn't necessary unless we're
- * going to try to set the time. Only super user
- * can actually set it, but anyone can look at the
- * one_sec count.
- */
- kmem_mode = O_RDONLY;
- if ( ! geteuid() && set_time )
- kmem_mode = O_RDWR;
-
- if ( set_time ) {
- mem_fd = open(kmem_name,kmem_mode,0);
- if ( -1 == mem_fd ) {
- perror(kmem_name);
- exit(1);
- }
- if ( -1 == nlist(unix_name,nl) ) {
- fprintf(stderr,"No name list for %s in %s\n",
- nl[0].n_name,unix_name);
- exit(1);
- }
- if ( -1L == lseek(mem_fd,nl[0].n_value,0) ) {
- perror("kmem lseek");
- exit(1);
- }
- if ( sizeof one_sec !=
- read(mem_fd,(char *) &one_sec,sizeof one_sec) ) {
- fprintf(stderr,"Bad read on %s\n",kmem_name);
- exit(1);
- }
- if ( debug )
- printf("At offset %x, %s is %d\n",nl[0].n_value,
- nl[0].n_name,one_sec);
- /*
- * Seek back to the one_sec location in case
- * we're going to write.
- */
- (void) lseek(mem_fd,nl[0].n_value,0);
- }
-
- /*
- * Now read the GC-1000 and display what it reports
- * along with the system time until an even second
- * is reached. At that point the system time will
- * be reset (if you have root privileges) and any
- * difference will be displayed.
- */
- if ( isatty(1) )
- printf("GC-1000 System\n");
- do {
- ret = rd_gc1000(tty_name,wwv_buf);
-
- /*
- * Tolerate MAX_TRIES failed attempts to
- * read the clock, then give up.
- */
- tries += ret;
- if ( MAX_TRIES == tries )
- char_time.tenths = '0';
-
- /*
- * There is no need to display the reading
- * if stdout isn't a tty or if there was
- * an error reading the clock.
- */
- if ( ! isatty(1) || ret )
- continue;
-
- char_time.cr = ' ';
- char_time.eos = '\0';
- sys_time = time ( (long *) 0 );
- time_now = localtime(&sys_time);
- (void) ascftime(time_buf,"%H:%M:%S %D",time_now);
- printf("\r%s %s",char_time.hour,time_buf);
- if ( set_time ) {
- (void) read(mem_fd,(char *) &one_sec,
- sizeof one_sec);
- printf(" one_sec %d ",one_sec);
- (void) lseek(mem_fd,nl[0].n_value,0);
- }
- fflush(stdout);
-
- } while ( char_time.tenths != '0' );
-
- fputc('\n',stdout);
-
- /*
- * Bail out if the clock didn't read OK
- */
- if ( MAX_TRIES == tries )
- exit(1);
-
- /*
- * We're through with the ASCII representation of
- * the WWV time, convert it to a time of day so
- * that we can report any difference or reset the
- * system time. Change the punctuation marks to
- * null characters.
- */
- char_time.colon_1 = char_time.colon_2 = char_time.point = '\0';
- wwv_time = atol(char_time.hour) * 3600L + atol(char_time.minute) * 60L;
- wwv_time += atol(char_time.second);
-
- /*
- * Now get the local time so that we can see
- * whether or not we're on time or how far off
- */
- time_now = localtime(&sys_time);
- now_time = (long) time_now->tm_hour * 3600L;
- now_time += (long) time_now->tm_min * 60L;
- now_time += (long) time_now->tm_sec;
- if ( wwv_time > now_time ) {
- diff_secs = wwv_time - now_time;
- t = "slow";
- sys_time += diff_secs;
- }
- if ( now_time > wwv_time ) {
- diff_secs = now_time - wwv_time;
- t = "fast";
- sys_time -= diff_secs;
- }
- if ( wwv_time == now_time ) {
- diff_secs = 0L;
- t = "\007on time!";
- }
-
- /*
- * Reset the system time and one_sec if authorized.
- * Don't reset the clock if the time difference is
- * greater than or equal to MAX_DIFF. It's quite
- * possible for the GC-1000 to be off by several
- * hours and still claim to be correct. If the system
- * time is actually off by more than MAX_DIFF, reset
- * it by hand using date(1).
- */
- if ( kmem_mode && diff_secs && diff_secs < MAX_DIFF ) {
- one_sec = HZ;
- (void) write(mem_fd,(char *) &one_sec, sizeof one_sec);
- (void) stime(&sys_time);
- t = "System time set to WWV";
- diff_secs = 0L;
- }
-
- if ( mem_fd )
- (void) close(mem_fd);
-
- /*
- * Now display the difference if any
- */
- if ( diff_secs )
- printf("%ld second(s) ",diff_secs);
- printf("%s\n",t);
- exit(0);
- }
-
- /*
- * Arrive here on SIGALRM, the various time out setjmps
- * will receive a 1 return from this catcher and succeed.
- */
- int time_out(sig_caught)
- int sig_caught;
- {
- if ( SIGALRM != sig_caught ) {
- fprintf(stderr,"Caught bogus signal %d\n",sig_caught);
- exit(1);
- }
-
- (void) signal(SIGALRM,SIG_IGN);
- (void) longjmp(timed_out,1);
- }
-
- /*
- * Subroutine to open and read the GC-1000 and
- * fill in the rs232 structure passed in.
- *
- * Note that the GC-1000 doesn't generate any signal
- * that is useful as DCD so O_NDELAY is used. It is
- * possible to invert and level shift the HI SPEC
- * signal (the one that lights the LED when synchronized
- * to within 10ms of WWV) from X101 pin 19 so that you
- * will simply time out if the clock isn't in HI SPEC.
- * If you do that, define HI_SPEC_IS_DCD and a blocking
- * open (with a five second time out) will handle it.
- */
- int rd_gc1000(tty_name,wwv_buf)
- char *tty_name;
- struct rs232 *wwv_buf;
- {
- char *s, c;
- int open_mode, count;
-
- open_mode = O_RDONLY;
- s = &wwv_buf->hour[0];
- *s = '\0';
- count = 1; /* Does double duty as an error return */
-
- #ifdef HI_SPEC_IS_DCD
- /*
- * Wrap a time out around a blocking open five
- * seconds seems reasonable.
- */
- (void) alarm(5);
- (void) signal(SIGALRM,time_out);
- if ( setjmp(timed_out) ) {
- fprintf(stderr,"Open time out on %s\n",tty_name);
- (void) alarm(0);
- return(1);
- }
- #else
- open_mode |= O_NDELAY;
- #endif
-
- wwv_fd = open(tty_name,open_mode,0);
-
- #ifdef HI_SPEC_IS_DCD
- (void) alarm(0);
- (void) signal(SIGALRM,SIG_IGN);
- #endif
-
- if ( -1 == wwv_fd ) {
- perror("gc1000 open");
- return(1);
- }
-
- /*
- * The tty port is open, set the baud rate
- */
- if ( -1 == ioctl(wwv_fd, TCSETA, &wwv_tty) ) {
- perror("gc1000 TCSETA");
- goto read_exit;
- }
-
- /*
- * Allow two seconds for the read to complete
- * although it will only take 25ms at 9600bps
- */
- (void) alarm(2);
- (void) signal(SIGALRM,time_out);
-
- if ( setjmp(timed_out) ) {
- fprintf(stderr,"\nRead time out on %s\n",tty_name);
- count = 1;
- goto read_exit;
- }
- count = 0;
-
- do {
- if ( read(wwv_fd,&c,1) ) {
- count++;
- *s++ = c & 0x7f;
- if ( '\r' == c )
- s--;
-
- *s = '\0';
- }
- } while ( count < 23 );
-
- s = &wwv_buf->hour[0];
- count = 0;
-
- if ( strlen(s) < 23 ) {
- fprintf(stderr,"HH:MM:SS.T MM/DD/YY\n%s|\n",s);
- count++;
- }
-
- read_exit:
- /*
- * Close everything and stop up signals
- */
- (void) alarm(0);
- (void) signal(SIGALRM,SIG_IGN);
- (void) close(wwv_fd);
- return(count);
- }
-