home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
The World of Computer Software
/
World_Of_Computer_Software-02-385-Vol-1of3.iso
/
x
/
xntp3.zip
/
xntpd
/
ntp_unixclock.
< prev
next >
Wrap
Text File
|
1992-08-29
|
15KB
|
616 lines
/*
* ntp_unixclock.c - routines for reading and adjusting a 4BSD-style
* system clock
*/
#include <stdio.h>
#include <nlist.h>
#include <sys/types.h>
#include <sys/time.h>
#include <sys/file.h>
#include <sys/stat.h>
#include <netinet/in.h>
#if defined(HPUX)
#include <sys/param.h>
#include <utmp.h>
#endif
#ifdef USELIBKVM
#include <kvm.h>
#include <limits.h>
#ifndef _POSIX2_LINE_MAX
#define _POSIX2_LINE_MAX 2048
#endif
#endif
#include "ntp_syslog.h"
#include "ntp_fp.h"
#include "ntp_unixtime.h"
#include "ntp.h"
#ifdef RS6000
#undef hz
#endif /* RS6000 */
extern int debug;
/*
* These routines (init_systime, get_systime, step_systime, adj_systime)
* implement an interface between the (more or less) system independent
* bits of NTP and the peculiarities of dealing with the Unix system
* clock. These routines will run with good precision fairly independently
* of your kernel's value of tickadj. I couldn't tell the difference
* between tickadj==40 and tickadj==5 on a microvax, though I prefer
* to set tickadj == 500/hz when in doubt. At your option you
* may compile this so that your system's clock is always slewed to the
* correct time even for large corrections. Of course, all of this takes
* a lot of code which wouldn't be needed with a reasonable tickadj and
* a willingness to let the clock be stepped occasionally. Oh well.
*/
/*
* Clock variables. We round calls to adjtime() to adj_precision
* microseconds, and limit the adjustment to tvu_maxslew microseconds
* (tsf_maxslew fractional sec) in one adjustment interval. As we are
* thus limited in the speed and precision with which we can adjust the
* clock, we compensate by keeping the known "error" in the system time
* in sys_offset. This is added to timestamps returned by get_systime().
* We also remember the clock precision we computed from the kernel in
* case someone asks us.
*/
long adj_precision; /* adj precision in usec (tickadj) */
long tvu_maxslew; /* maximum adjust doable in 1<<CLOCK_ADJ sec (usec) */
unsigned long tsf_maxslew; /* same as above, as long format */
static l_fp sys_offset; /* correction for current system time */
/*
* Import sys_clock (it is updated in get_systime)
*/
extern long sys_clock;
/*
* Tables for converting between time stamps and struct timeval's
* are in the library. Reference them here.
*/
extern u_long ustotslo[];
extern u_long ustotsmid[];
extern u_long ustotshi[];
extern long tstoushi[];
extern long tstousmid[];
extern long tstouslo[];
/*
* init_systime - initialize the system clock support code, return
* clock precision.
*
* Note that this code obtains to kernel variables related to the local
* clock, tickadj and tick. The code knows how the Berkeley adjtime
* call works, and assumes these two variables are obtainable and are
* used in the same manner. Tick is supposed to be the number of
* microseconds which are added to the system clock at clock interrupt
* time when the time isn't being slewed. Tickadj is supposed to be
* the number of microseconds which are added or subtracted from tick when
* the time is being slewed.
*
* If either of these two variables is missing, or is there but is used
* for a purpose different than that described, you are SOL and may have
* to do some custom kludging.
*
* This really shouldn't be in here.
*/
void
init_systime()
{
u_long tickadj;
u_long tick;
u_long hz;
void clock_parms();
/*
* Obtain the values
*/
clock_parms(&tickadj, &tick);
#ifdef DEBUG
if (debug)
printf("kernel vars: tickadj = %d, tick = %d\n", tickadj, tick);
#endif
/*
* If tickadj or hz wasn't found, we're doomed. If hz is
* unreasonably small, forget it.
*/
if (tickadj == 0 || tick == 0) {
syslog(LOG_ERR, "tickadj or tick unknown, exiting");
exit(3);
}
if (tick > 65535) {
syslog(LOG_ERR, "tick value of %lu is unreasonably large",
tick);
exit(3);
}
/*
* Estimate hz from tick
*/
hz = 1000000L / tick;
/*
* Set adj_precision and the maximum slew based on this. Note
* that maxslew is set slightly shorter than it needs to be as
* insurance that all slews requested will complete in 1<<CLOCK_ADJ
* seconds.
*/
#ifdef ADJTIME_IS_ACCURATE
adj_precision = 1;
#else
adj_precision = tickadj;
#endif /* ADJTIME_IS_ACCURATE */
tvu_maxslew = tickadj * (hz-1) * (1<<CLOCK_ADJ);
if (tvu_maxslew > 999990) {
/*
* Don't let the maximum slew exceed 1 second in 4. This
* simplifies calculations a lot since we can then deal
* with less-than-one-second fractions.
*/
tvu_maxslew = (999990/adj_precision) * adj_precision;
}
TVUTOTSF(tvu_maxslew, tsf_maxslew);
#ifdef DEBUG
if (debug)
printf(
"adj_precision = %d, tvu_maxslew = %d, tsf_maxslew = 0.%08x\n",
adj_precision, tvu_maxslew, tsf_maxslew);
#endif
/*
* Set the current offset to 0
*/
sys_offset.l_ui = sys_offset.l_uf = 0;
}
/*
* get_systime - return the system time in timestamp format
* As a side effect, update sys_clock.
*/
void
get_systime(ts)
l_fp *ts;
{
struct timeval tv;
#if !defined(SLEWALWAYS) && !defined(LARGETICKADJ)
/*
* Quickly get the time of day and convert it
*/
(void) gettimeofday(&tv, (struct timezone *)NULL);
TVTOTS(&tv, ts);
ts->l_uf += TS_ROUNDBIT; /* guaranteed not to overflow */
#else
/*
* Get the time of day, convert to time stamp format
* and add in the current time offset. Then round
* appropriately.
*/
(void) gettimeofday(&tv, (struct timezone *)NULL);
TVTOTS(&tv, ts);
L_ADD(ts, &sys_offset);
if (ts->l_uf & TS_ROUNDBIT)
L_ADDUF(ts, (unsigned long) TS_ROUNDBIT);
#endif /* !defined(SLEWALWAYS) && !defined(LARGETICKADJ) */
ts->l_ui += JAN_1970;
ts->l_uf &= TS_MASK;
sys_clock = ts->l_ui;
}
/*
* step_systime - do a step adjustment in the system time (at least from
* NTP's point of view.
*/
void
step_systime(ts)
l_fp *ts;
{
#ifndef SLEWALWAYS
struct timeval timetv, adjtv;
int isneg = 0;
#if defined(HPUX)
struct utmp ut;
time_t oldtime;
#endif
extern char *lfptoa();
extern char *tvtoa();
extern char *utvtoa();
/*
* We can afford to be sloppy here since if this is called
* the time is really screwed and everything is being reset.
*/
L_ADD(&sys_offset, ts);
if (L_ISNEG(&sys_offset)) {
isneg = 1;
L_NEG(&sys_offset);
}
TSTOTV(&sys_offset, &adjtv);
(void) gettimeofday(&timetv, (struct timezone *)NULL);
#if defined(HPUX)
oldtime = timetv.tv_sec;
#endif
#ifdef DEBUG
if (debug > 3)
printf("step: %s, sys_offset = %s, adjtv = %s, timetv = %s\n",
lfptoa(ts, 9), lfptoa(&sys_offset, 9), tvtoa(&adjtv),
utvtoa(&timetv));
#endif
if (isneg) {
timetv.tv_sec -= adjtv.tv_sec;
timetv.tv_usec -= adjtv.tv_usec;
if (timetv.tv_usec < 0) {
timetv.tv_sec--;
timetv.tv_usec += 1000000;
}
} else {
timetv.tv_sec += adjtv.tv_sec;
timetv.tv_usec += adjtv.tv_usec;
if (timetv.tv_usec >= 1000000) {
timetv.tv_sec++;
timetv.tv_usec -= 1000000;
}
}
if (settimeofday(&timetv, (struct timezone *)NULL) != 0)
syslog(LOG_ERR, "Can't set time of day: %m");
#ifdef DEBUG
if (debug > 3)
printf("step: new timetv = %s\n", utvtoa(&timetv));
#endif
sys_offset.l_ui = sys_offset.l_uf = 0;
#if defined(HPUX)
_clear_adjtime();
/*
* Write old and new time entries in utmp and wtmp if step adjustment
* is greater than one second.
*/
if (oldtime != timetv.tv_sec) {
bzero((char *)&ut, sizeof(ut));
ut.ut_type = OLD_TIME;
ut.ut_time = oldtime;
(void)strcpy(ut.ut_line, OTIME_MSG);
pututline(&ut);
setutent();
ut.ut_type = NEW_TIME;
ut.ut_time = timetv.tv_sec;
(void)strcpy(ut.ut_line, NTIME_MSG);
pututline(&ut);
utmpname(WTMP_FILE);
ut.ut_type = OLD_TIME;
ut.ut_time = oldtime;
(void)strcpy(ut.ut_line, OTIME_MSG);
pututline(&ut);
ut.ut_type = NEW_TIME;
ut.ut_time = timetv.tv_sec;
(void)strcpy(ut.ut_line, NTIME_MSG);
pututline(&ut);
endutent();
}
#endif
#else
/*
* Just add adjustment into the current offset. The update
* routine will take care of bringing the system clock into
* line.
*/
L_ADD(&sys_offset, ts);
#endif /* SLEWALWAYS */
}
/*
* adj_systime - called once every 1<<CLOCK_ADJ seconds to make system time
* adjustments.
*/
void
adj_systime(adj)
long adj;
{
register unsigned long offset_i, offset_f;
register long temp;
register unsigned long residual;
register int isneg = 0;
struct timeval adjtv, oadjtv;
extern char *mfptoa();
extern char *umfptoa();
extern char *utvtoa();
extern char *tvtoa();
/*
* Move the current offset into the registers
*/
offset_i = sys_offset.l_ui;
offset_f = sys_offset.l_uf;
/*
* Add the new adjustment into the system offset. Adjust the
* system clock to minimize this.
*/
M_ADDF(offset_i, offset_f, adj);
if (M_ISNEG(offset_i, offset_f)) {
isneg = 1;
M_NEG(offset_i, offset_f);
}
#ifdef DEBUG
if (debug > 4)
printf("adj_systime(%s): offset = %s%s\n",
mfptoa((adj<0?-1:0), adj, 9), isneg?"-":"",
umfptoa(offset_i, offset_f, 9));
#endif
adjtv.tv_sec = 0;
if (offset_i > 0 || offset_f >= tsf_maxslew) {
/*
* Slew is bigger than we can complete in
* the adjustment interval. Make a maximum
* sized slew and reduce sys_offset by this
* much.
*/
M_SUBUF(offset_i, offset_f, tsf_maxslew);
if (!isneg) {
adjtv.tv_usec = tvu_maxslew;
} else {
adjtv.tv_usec = -tvu_maxslew;
M_NEG(offset_i, offset_f);
}
#ifdef DEBUG
if (debug > 4)
printf(
"maximum slew: %s%s, remainder = %s\n",
isneg?"-":"", umfptoa(0, tsf_maxslew, 9),
mfptoa(offset_i, offset_f, 9));
#endif
} else {
/*
* We can do this slew in the time period. Do our
* best approximation (rounded), save residual for
* next adjustment.
*
* Note that offset_i is guaranteed to be 0 here.
*/
TSFTOTVU(offset_f, temp);
#ifndef ADJTIME_IS_ACCURATE
/*
* Round value to be an even multiple of adj_precision
*/
residual = temp % adj_precision;
temp -= residual;
if (residual << 1 >= adj_precision)
temp += adj_precision;
#endif /* ADJTIME_IS_ACCURATE */
TVUTOTSF(temp, residual);
M_SUBUF(offset_i, offset_f, residual);
if (isneg) {
adjtv.tv_usec = -temp;
M_NEG(offset_i, offset_f);
} else {
adjtv.tv_usec = temp;
}
#ifdef DEBUG
if (debug > 4)
printf(
"slew adjtv = %s, adjts = %s, sys_offset = %s\n",
tvtoa(&adjtv), umfptoa(0, residual, 9),
mfptoa(offset_i, offset_f, 9));
#endif
}
sys_offset.l_ui = offset_i;
sys_offset.l_uf = offset_f;
if (adjtv.tv_usec == 0)
return;
if (adjtime(&adjtv, &oadjtv) != 0)
syslog(LOG_ERR, "Can't do time adjustment: %m");
#ifdef DEBUGRS6000
syslog(LOG_ERR, "adj_systime(%s): offset = %s%s\n",
mfptoa((adj<0?-1:0), adj, 9), isneg?"-":"",
umfptoa(offset_i, offset_f, 9));
syslog(LOG_ERR, "%d %d %d %d\n", (int) adjtv.tv_sec,
(int) adjtv.tv_usec, (int) oadjtv.tv_sec, (int)
oadjtv.tv_usec);
#endif /* DEBUGRS6000 */
if (oadjtv.tv_sec != 0 || oadjtv.tv_usec != 0) {
syslog(LOG_ERR, "Previous time adjustment didn't complete");
#ifdef DEBUG
if (debug > 4)
printf(
"Previous adjtime() incomplete, residual = %s\n",
tvtoa(&oadjtv));
#endif
}
}
#ifdef USELIBKVM
/*
* clock_parms - return the local clock tickadj and tick parameters
*
* This version uses the SunOS libkvm (or the bsd compatability version).
*/
void
clock_parms(tickadj, tick)
u_long *tickadj;
u_long *tick;
{
register int n;
register kvm_t *kd;
static struct nlist nl[] = {
#define N_TICKADJ 0
{ "_tickadj" },
#define N_TICK 1
{ "_tick" },
{ "" },
};
if ((kd = kvm_open(NULL, NULL, NULL, O_RDONLY, NULL)) == NULL) {
syslog(LOG_ERR, "kvm_open failed");
exit(3);
}
if (kvm_nlist(kd, nl) != 0) {
syslog(LOG_ERR, "kvm_nlist failed");
exit(3);
}
if (kvm_read(kd, nl[N_TICKADJ].n_value, tickadj, sizeof(*tickadj)) !=
sizeof(*tickadj)) {
syslog(LOG_ERR, "kvm_read tickadj failed");
exit(3);
}
if (kvm_read(kd, nl[N_TICK].n_value, tick, sizeof(*tick)) !=
sizeof(*tick)) {
syslog(LOG_ERR, "kvm_read tick failed");
exit(3);
}
if (kvm_close(kd) < 0) {
syslog(LOG_ERR, "kvm_close failed");
exit(3);
}
#undef N_TICKADJ
#undef N_TICK
}
#endif /* USELIBKVM */
#ifdef READKMEM
/*
* clock_parms - return the local clock tickadj and tick parameters
*
* Note that this version grovels about in /dev/kmem to determine
* these values. This probably should be elsewhere.
*/
void
clock_parms(tickadj, tick)
u_long *tickadj;
u_long *tick;
{
register int i;
int kmem;
#ifdef NeXT
#define N_NAME n_un.n_name
static struct nlist nl[] =
{ {{"_tickadj"}},
{{"_tick"}},
{{""}},
};
#else
#define N_NAME n_name
static struct nlist nl[] =
{ {"_tickadj"},
{"_tick"},
{""},
};
#endif
static char *kernelnames[] = {
"/vmunix",
"/unix",
"/mach",
"/386bsd",
#ifdef KERNELFILE
KERNELFILE,
#endif
NULL
};
struct stat stbuf;
int vars[2];
#define K_TICKADJ 0
#define K_TICK 1
/*
* Read clock parameters from kernel
*/
kmem = open("/dev/kmem", O_RDONLY);
if (kmem < 0) {
syslog(LOG_ERR, "Can't open /dev/kmem for reading: %m");
#ifdef DEBUG
if (debug)
perror("/dev/kmem");
#endif
exit(3);
}
for (i = 0; kernelnames[i] != NULL; i++) {
if (stat(kernelnames[i], &stbuf) == -1)
continue;
if (nlist(kernelnames[i], nl) >= 0)
break;
}
if (kernelnames[i] == NULL) {
syslog(LOG_ERR,
"Clock init couldn't find kernel as either /vmunix or /unix");
exit(3);
}
for (i = 0; i < (sizeof(vars)/sizeof(vars[0])); i++) {
off_t where;
vars[i] = 0;
if ((where = nl[i].n_value) == 0) {
syslog(LOG_ERR, "Unknown kernal var %s",
nl[i].N_NAME);
continue;
}
if (lseek(kmem, where, L_SET) == -1) {
syslog(LOG_ERR, "lseek for %s fails: %m",
nl[i].N_NAME);
continue;
}
if (read(kmem, &vars[i], sizeof(int)) != sizeof(int)) {
syslog(LOG_ERR, "read for %s fails: %m",
nl[i].N_NAME);
}
}
close(kmem);
*tickadj = (u_long)vars[K_TICKADJ];
*tick = (u_long)vars[K_TICK];
#undef K_TICKADJ
#undef K_TICK
#undef N_NAME
}
#endif /* READKMEM */
#ifdef NOKMEM
/*
* clock_parms - return the local clock tickadj and tick parameters
*
* Note that this version uses static values!
*/
void
clock_parms(tickadj, tick)
u_long *tickadj;
u_long *tick;
{
#ifndef HZ
#define HZ 60
#endif
#ifdef RS6000
*tickadj = 1000;
#else
*tickadj = 500 / HZ;
#endif /*RS6000*/
*tick = 1000000L / HZ;
#ifdef DEBUG
if (debug)
printf("NOTE: Using preset values for tick and tickadj !!\n");
#endif
}
#endif /*NOKMEM*/