NetBSD/lib/libntp/systime.c
sommerfeld d6bfb9f33e Fix LP64 glitch which resulted in xntpd attempting pass excessively
large offsets into adjtime().  (bug reported in PR7602).

It's fairly clear that this is not the only lp64 glitch in the code,
but this is a biggie.
1999-10-09 19:42:46 +00:00

581 lines
14 KiB
C

/* $NetBSD: systime.c,v 1.6 1999/10/09 19:42:46 sommerfeld Exp $ */
/*
* systime -- routines to fiddle a UNIX clock.
*/
#ifdef HAVE_CONFIG_H
# include <config.h>
#endif
#include <stdio.h>
#include <sys/types.h>
#include <sys/time.h>
#ifdef HAVE_SYS_PARAM_H
# include <sys/param.h>
#endif
#ifdef HAVE_UTMP_H
# include <utmp.h>
#endif /* HAVE_UTMP_H */
#ifdef HAVE_UTMPX_H
# include <utmpx.h>
#endif /* HAVE_UTMPX_H */
#include "ntp_fp.h"
#include "ntp_syslog.h"
#include "ntp_unixtime.h"
#include "ntp_stdlib.h"
#if defined(GDT_SURVEYING)
l_fp gdt_rsadj; /* running sum of adjustments to time */
#endif
#if defined(STEP_SLEW)
# define SLEWALWAYS
#endif
extern int debug;
int allow_set_backward;
/*
* 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 second */
u_long tsf_maxslew; /* same as above, as long format */
l_fp sys_clock_offset; /* correction for current system time */
#ifdef SYS_WINNT
/*
* number of 100 nanosecond units added to the clock at each tick
* determined by GetSystemTimeAdjustment() in clock_parms()
*/
long units_per_tick;
#endif /* SYS_WINNT */
/*
* get_systime - return the system time in timestamp format
* As a side effect, update sys_clock.
*/
void
get_systime(now)
l_fp *now;
{
struct timeval tv;
#ifdef HAVE_GETCLOCK
struct timespec ts;
#endif
/*
* Get the time of day, convert to time stamp format
* and add in the current time offset. Then round
* appropriately.
*/
#ifdef HAVE_GETCLOCK
(void) getclock(TIMEOFDAY, &ts);
tv.tv_sec = ts.tv_sec;
tv.tv_usec = ts.tv_nsec / 1000;
#else /* not HAVE_GETCLOCK */
(void) GETTIMEOFDAY(&tv, (struct timezone *)0);
#endif /* not HAVE_GETCLOCK */
TVTOTS(&tv, now);
L_ADD(now, &sys_clock_offset);
if (now->l_uf & TS_ROUNDBIT)
L_ADDUF(now, TS_ROUNDBIT);
now->l_ui += JAN_1970;
now->l_uf &= TS_MASK;
sys_clock = now->l_ui;
}
/*
* step_systime - do a step adjustment in the system time (at least from
* NTP's point of view.
*/
int
step_systime(now)
l_fp *now;
{
register u_long tmp_ui;
register u_long tmp_uf;
int isneg;
#ifdef STEP_SLEW
int n;
#endif
/*
* Take the absolute value of the offset
*/
tmp_ui = now->l_ui;
tmp_uf = now->l_uf;
if (M_ISNEG(tmp_ui, tmp_uf)) {
M_NEG(tmp_ui, tmp_uf);
isneg = 1;
} else
isneg = 0;
#ifdef SLEWALWAYS
#ifdef STEP_SLEW
if (tmp_ui >= 3) { /* Step it and slew we might win */
n = step_systime_real(now);
if (!n) return n;
if (isneg)
now->l_ui = ~0;
else
now->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, now);
#if defined(GDT_SURVEYING)
L_ADD(&gdt_rsadj, now);
#endif
return 1;
#else /* SLEWALWAYS */
#ifdef DEBUG
if (debug > 2)
printf ("allow_set_backward=%d\n",allow_set_backward);
#endif
if (isneg && !allow_set_backward) {
L_ADD(&sys_clock_offset, now);
return 1;
}
else {
#ifdef DEBUG
if (debug > 2)
printf ("calling step_systime_real from not slewalways\n");
#endif
return step_systime_real(now);
}
#endif /* SLEWALWAYS */
}
int max_no_complete = 20;
/*
* adj_systime - called once every second to make system time adjustments.
*/
int
adj_systime(now)
l_fp *now;
{
register u_int32 offset_i, offset_f;
register long temp;
register u_long residual;
register int isneg = 0;
struct timeval adjtv;
#ifndef SYS_WINNT
struct timeval oadjtv;
l_fp oadjts;
#endif
long adj = now->l_f;
int rval;
#ifdef SYS_WINNT
DWORD dwTimeAdjustment;
#endif /* SYS_WINNT */
#if defined(GDT_SURVEYING)
/* add to record of increments */
M_ADDF(gdt_rsadj.l_ui, gdt_rsadj.l_uf, adj);
#endif
#ifdef DEBUG
if (debug > 4)
printf("systime: offset %s\n", lfptoa(now, 6));
#endif
/*
* 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);
}
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) {
#ifndef SYS_WINNT
adjtv.tv_usec = tvu_maxslew;
#else
dwTimeAdjustment = units_per_tick + tvu_maxslew / adj_precision;
#endif /* SYS_WINNT */
} else {
#ifndef SYS_WINNT
adjtv.tv_usec = -tvu_maxslew;
#else
dwTimeAdjustment = units_per_tick - tvu_maxslew / adj_precision;
#endif /* SYS_WINNT */
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 ( (long) (residual << 1) >= adj_precision)
temp += adj_precision;
#endif /* ADJTIME_IS_ACCURATE */
TVUTOTSF(temp, residual);
M_SUBUF(offset_i, offset_f, residual);
if (isneg) {
#ifndef SYS_WINNT
adjtv.tv_usec = -temp;
#else
dwTimeAdjustment = units_per_tick - temp / adj_precision;
#endif /* SYS_WINNT */
M_NEG(offset_i, offset_f);
} else {
#ifndef SYS_WINNT
adjtv.tv_usec = temp;
#else
dwTimeAdjustment = units_per_tick + temp / adj_precision;
#endif /* SYS_WINNT */
}
#ifdef DEBUG
if (debug > 4)
#ifndef SYS_WINNT
printf(
"systime: adjtv = %s sec, adjts = %s sec, sys_clock_offset = %s sec\n",
tvtoa(&adjtv), umfptoa(0, residual, 6),
mfptoa(offset_i, offset_f, 6));
#else
printf(
"systime: dwTimeAdjustment = %d, sys_clock_offset = %s sec\n",
dwTimeAdjustment, mfptoa(offset_i, offset_f, 6));
#endif /* SYS_WINNT */
#endif /* DEBUG */
}
/*
* Here we do the actual adjustment. If for some reason the adjtime()
* call fails, like it is not implemented or something like that,
* we honk to the log. If the previous adjustment did not complete,
* we correct the residual offset and honk to the log, but only for
* a little while.
*/
if (
#ifndef SYS_WINNT
/* casey - we need a posix type thang here */
(adjtime(&adjtv, &oadjtv) < 0)
#else
(!SetSystemTimeAdjustment(dwTimeAdjustment, FALSE))
#endif /* SYS_WINNT */
) {
msyslog(LOG_ERR, "Can't adjust time: %m");
rval = 0;
} else {
sys_clock_offset.l_ui = offset_i;
sys_clock_offset.l_uf = offset_f;
rval = 1;
#ifndef SYS_WINNT
if (oadjtv.tv_sec != 0 || oadjtv.tv_usec != 0) {
sTVTOTS(&oadjtv, &oadjts);
L_ADD(&sys_clock_offset, &oadjts);
#if defined(GDT_SURVEYING)
L_ADD(&gdt_rsadj, &oadjts);
#endif
if (max_no_complete > 0) {
max_no_complete--;
NLOG(NLOG_SYSSTATUS|NLOG_SYNCSTATUS)
msyslog(LOG_WARNING,
"Previous time adjustment incomplete; residual %s sec\n",
lfptoa(&oadjts, 6));
}
}
#endif /* SYS_WINNT */
}
return(rval);
}
/*
* This is used by ntpdate even when xntpd does not use it! WLJ
*/
int
step_systime_real(now)
l_fp *now;
{
struct timeval timetv, adjtv, oldtimetv;
int isneg = 0;
#if defined(HAVE_GETCLOCK) || defined(HAVE_CLOCK_SETTIME)
struct timespec ts;
#endif
#if DEBUG
if (debug)
printf("step_systime: offset %s sys_offset %s\n",
lfptoa(now, 6), lfptoa(&sys_clock_offset, 6));
#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, now);
#if defined(GDT_SURVEYING)
L_ADD(&gdt_rsadj, now);
#endif
if (L_ISNEG(&sys_clock_offset)) {
isneg = 1;
L_NEG(&sys_clock_offset);
}
TSTOTV(&sys_clock_offset, &adjtv);
#ifdef HAVE_GETCLOCK
(void) getclock(TIMEOFDAY, &ts);
timetv.tv_sec = ts.tv_sec;
timetv.tv_usec = ts.tv_nsec / 1000;
#else /* not HAVE_GETCLOCK */
(void) GETTIMEOFDAY(&timetv, (struct timezone *)0);
#endif /* not HAVE_GETCLOCK */
oldtimetv = timetv;
#ifdef DEBUG
if (debug)
printf("step: %s sec, sys_clock_offset = %s sec, adjtv = %s sec, timetv = %s sec\n",
lfptoa(now, 6), lfptoa(&sys_clock_offset, 6), 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;
}
}
#ifdef DEBUG
if (debug)
printf("step: old timetv = %s sec\n", utvtoa(&timetv));
#endif
#if HAVE_CLOCK_SETTIME
ts.tv_sec = timetv.tv_sec;
ts.tv_nsec = timetv.tv_usec * 1000;
#endif /* HAVE_CLOCK_SETTIME */
if (
#if HAVE_CLOCK_SETTIME
(clock_settime(CLOCK_REALTIME, &ts) != 0)
#else /* HAVE_CLOCK_SETTIME */
(SETTIMEOFDAY(&timetv, (struct timezone *)0) != 0)
#endif /* HAVE_CLOCK_SETTIME */
) {
msyslog(LOG_ERR, "Can't set time of day: %m");
return (0);
}
#if DEBUG
if (debug) {
#ifdef HAVE_GETCLOCK
(void) getclock(TIMEOFDAY, &ts);
timetv.tv_sec = ts.tv_sec;
timetv.tv_usec = ts.tv_nsec / 1000;
#else /* not HAVE_GETCLOCK */
(void) GETTIMEOFDAY(&timetv, (struct timezone *)0);
#endif /* not HAVE_GETCLOCK */
printf("step: new timetv = %s sec\n", utvtoa(&timetv));
}
#endif
L_CLR(&sys_clock_offset);
#ifdef NEED_HPUX_ADJTIME
/*
* CHECKME: is this correct when called by ntpdate?????
*/
_clear_adjtime();
#endif
/*
* FreeBSD, for example, has:
* struct utmp {
* char ut_line[UT_LINESIZE];
* char ut_name[UT_NAMESIZE];
* char ut_host[UT_HOSTSIZE];
* long ut_time;
* };
* and appends line="|", name="date", host="", time for the OLD
* and appends line="{", name="date", host="", time for the NEW
* to _PATH_WTMP .
*
* Some OSes have utmp, some have utmpx.
*/
/*
* Write old and new time entries in utmp and wtmp if step adjustment
* is greater than one second.
*
* This might become even Uglier...
*/
if (oldtimetv.tv_sec != timetv.tv_sec)
{
#ifdef HAVE_UTMP_H
struct utmp ut;
#endif
#ifdef HAVE_UTMPX_H
struct utmpx utx;
#endif
#ifdef HAVE_UTMP_H
memset((char *)&ut, 0, sizeof(ut));
#endif
#ifdef HAVE_UTMPX_H
memset((char *)&utx, 0, sizeof(utx));
#endif
/* UTMP */
#ifdef UPDATE_UTMP
# ifdef HAVE_PUTUTLINE
ut.ut_type = OLD_TIME;
(void)strcpy(ut.ut_line, OTIME_MSG);
ut.ut_time = oldtimetv.tv_sec;
pututline(&ut);
setutent();
ut.ut_type = NEW_TIME;
(void)strcpy(ut.ut_line, NTIME_MSG);
ut.ut_time = timetv.tv_sec;
pututline(&ut);
endutent();
# else /* not HAVE_PUTUTLINE */
# endif /* not HAVE_PUTUTLINE */
#endif /* UPDATE_UTMP */
/* UTMPX */
#ifdef UPDATE_UTMPX
# ifdef HAVE_PUTUTXLINE
utx.ut_type = OLD_TIME;
(void)strcpy(utx.ut_line, OTIME_MSG);
utx.ut_tv = oldtimetv;
pututxline(&utx);
setutxent();
utx.ut_type = NEW_TIME;
(void)strcpy(utx.ut_line, NTIME_MSG);
utx.ut_tv = timetv;
pututxline(&utx);
endutxent();
# else /* not HAVE_PUTUTXLINE */
# endif /* not HAVE_PUTUTXLINE */
#endif /* UPDATE_UTMPX */
/* WTMP */
#ifdef UPDATE_WTMP
# ifdef HAVE_PUTUTLINE
utmpname(WTMP_FILE);
ut.ut_type = OLD_TIME;
(void)strcpy(ut.ut_line, OTIME_MSG);
ut.ut_time = oldtimetv.tv_sec;
pututline(&ut);
ut.ut_type = NEW_TIME;
(void)strcpy(ut.ut_line, NTIME_MSG);
ut.ut_time = timetv.tv_sec;
pututline(&ut);
endutent();
# else /* not HAVE_PUTUTLINE */
# endif /* not HAVE_PUTUTLINE */
#endif /* UPDATE_WTMP */
/* WTMPX */
#ifdef UPDATE_WTMPX
# ifdef HAVE_PUTUTXLINE
utx.ut_type = OLD_TIME;
utx.ut_tv = oldtimetv;
(void)strcpy(utx.ut_line, OTIME_MSG);
# ifdef HAVE_UPDWTMPX
updwtmpx(WTMPX_FILE, &utx);
# else /* not HAVE_UPDWTMPX */
# endif /* not HAVE_UPDWTMPX */
# else /* not HAVE_PUTUTXLINE */
# endif /* not HAVE_PUTUTXLINE */
# ifdef HAVE_PUTUTXLINE
utx.ut_type = NEW_TIME;
utx.ut_tv = timetv;
(void)strcpy(utx.ut_line, NTIME_MSG);
# ifdef HAVE_UPDWTMPX
updwtmpx(WTMPX_FILE, &utx);
# else /* not HAVE_UPDWTMPX */
# endif /* not HAVE_UPDWTMPX */
# else /* not HAVE_PUTUTXLINE */
# endif /* not HAVE_PUTUTXLINE */
#endif /* UPDATE_WTMPX */
}
return (1);
}