freebsd-dev/usr.sbin/xntpd/lib/systime.c
1995-05-30 03:57:47 +00:00

377 lines
9.2 KiB
C

/*
* systime -- routines to fiddle a UNIX clock.
*/
#include <stdio.h>
#include <sys/types.h>
#include <sys/time.h>
#if defined(SYS_HPUX) || defined(sgi) || defined(SYS_BSDI) || defined(SYS_44BSD)
#include <sys/param.h>
#include <utmp.h>
#endif
#ifdef SYS_LINUX
#include "sys/timex.h"
#endif
#include "ntp_fp.h"
#include "ntp_syslog.h"
#include "ntp_unixtime.h"
#include "ntp_stdlib.h"
#if defined(STEP_SLEW)
#define SLEWALWAYS
#endif
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_clock_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 sys_clock;
long adj_precision; /* adj precision in usec (tickadj) */
long tvu_maxslew; /* maximum adjust doable in 1<<CLOCK_ADJ sec (usec) */
u_long tsf_maxslew; /* same as above, as long format */
l_fp sys_clock_offset; /* correction for current system time */
/*
* 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)
/*
* Quickly get the time of day and convert it
*/
(void) GETTIMEOFDAY(&tv, (struct timezone *)0);
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 *)0);
TVTOTS(&tv, ts);
L_ADD(ts, &sys_clock_offset);
if (ts->l_uf & TS_ROUNDBIT)
L_ADDUF(ts, TS_ROUNDBIT);
#endif /* !defined(SLEWALWAYS) */
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.
*/
int
step_systime(ts)
l_fp *ts;
{
#ifdef SLEWALWAYS
#ifdef STEP_SLEW
register u_long tmp_ui;
register u_long tmp_uf;
int isneg;
int n;
/*
* Take the absolute value of the offset
*/
tmp_ui = ts->l_ui;
tmp_uf = ts->l_uf;
if (M_ISNEG(tmp_ui, tmp_uf)) {
M_NEG(tmp_ui, tmp_uf);
isneg = 1;
} else
isneg = 0;
if (tmp_ui >= 3) { /* Step it and slew we might win */
n = step_systime_real(ts);
if (!n) return n;
if (isneg)
ts->l_ui = ~0;
else
ts->l_ui = ~0;
}
#endif
/*
* Just add adjustment into the current offset. The update
* routine will take care of bringing the system clock into
* line.
*/
L_ADD(&sys_clock_offset, ts);
return 1;
#else /* SLEWALWAYS */
return step_systime_real(ts);
#endif /* SLEWALWAYS */
}
int max_no_complete = 20;
/*
* adj_systime - called once every 1<<CLOCK_ADJ seconds to make system time
* adjustments.
*/
int
adj_systime(ts)
l_fp *ts;
{
register u_long offset_i, offset_f;
register long temp;
register u_long residual;
register int isneg = 0;
struct timeval adjtv, oadjtv;
l_fp oadjts;
long adj = ts->l_f;
int rval;
adjtv.tv_sec = adjtv.tv_usec = 0;
/*
* Move the current offset into the registers
*/
offset_i = sys_clock_offset.l_ui;
offset_f = sys_clock_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)
syslog(LOG_DEBUG, "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_clock_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("systime: 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(
"systime: adjtv = %s, adjts = %s, sys_clock_offset = %s\n",
tvtoa(&adjtv), umfptoa(0, residual, 9),
mfptoa(offset_i, offset_f, 9));
#endif
}
if (adjtime(&adjtv, &oadjtv) < 0) {
syslog(LOG_ERR, "Can't do time adjustment: %m");
rval = 0;
} else {
sys_clock_offset.l_ui = offset_i;
sys_clock_offset.l_uf = offset_f;
rval = 1;
#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) {
sTVTOTS(&oadjtv, &oadjts);
L_ADD(&sys_clock_offset, &oadjts);
if (max_no_complete > 0) {
syslog(LOG_WARNING,
"Previous time adjustment didn't complete");
#ifdef DEBUG
if (debug > 4)
syslog(LOG_DEBUG,
"Previous adjtime() incomplete, residual = %s\n",
tvtoa(&oadjtv));
#endif
if (--max_no_complete == 0)
syslog(LOG_WARNING,
"*** No more 'Prev time adj didn't complete'");
}
}
}
return(rval);
}
/*
* This is used by ntpdate even when xntpd does not use it! WLJ
*/
int
step_systime_real(ts)
l_fp *ts;
{
struct timeval timetv, adjtv;
int isneg = 0;
#if defined(SYS_HPUX)
struct utmp ut;
time_t oldtime;
#endif
/*
* 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_clock_offset, ts);
if (L_ISNEG(&sys_clock_offset)) {
isneg = 1;
L_NEG(&sys_clock_offset);
}
TSTOTV(&sys_clock_offset, &adjtv);
(void) GETTIMEOFDAY(&timetv, (struct timezone *)0);
#if defined(SYS_HPUX)
oldtime = timetv.tv_sec;
#endif
#ifdef DEBUG
if (debug > 3)
syslog(LOG_DEBUG, "step: %s, sys_clock_offset = %s, adjtv = %s, timetv = %s\n",
lfptoa(ts, 9), lfptoa(&sys_clock_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 *)0) != 0) {
syslog(LOG_ERR, "Can't set time of day: %m");
return 0;
}
#ifdef DEBUG
if (debug > 3)
syslog(LOG_DEBUG, "step: new timetv = %s\n", utvtoa(&timetv));
#endif
sys_clock_offset.l_ui = sys_clock_offset.l_uf = 0;
#if defined(SYS_HPUX)
#if (SYS_HPUX < 10)
/*
* CHECKME: is this correct when called by ntpdate?????
*/
_clear_adjtime();
#endif
/*
* Write old and new time entries in utmp and wtmp if step adjustment
* is greater than one second.
*/
if (oldtime != timetv.tv_sec) {
memset((char *)&ut, 0, 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
return 1;
}