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