581 lines
14 KiB
C
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);
|
|
}
|