diff options
Diffstat (limited to 'libntp/systime.c')
-rw-r--r-- | libntp/systime.c | 521 |
1 files changed, 521 insertions, 0 deletions
diff --git a/libntp/systime.c b/libntp/systime.c new file mode 100644 index 000000000000..ac6aa983f7b5 --- /dev/null +++ b/libntp/systime.c @@ -0,0 +1,521 @@ +/* + * systime -- routines to fiddle a UNIX clock. + * + * ATTENTION: Get approval from Dave Mills on all changes to this file! + * + */ +#include "ntp_machine.h" +#include "ntp_fp.h" +#include "ntp_syslog.h" +#include "ntp_unixtime.h" +#include "ntp_stdlib.h" + +#ifdef SIM +#include "ntpsim.h" +#endif /*SIM */ + +#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 */ + +/* + * These routines (get_systime, step_systime, adj_systime) implement an + * interface between the system independent NTP clock and the Unix + * system clock in various architectures and operating systems. + * + * Time is a precious quantity in these routines and every effort is + * made to minimize errors by always rounding toward zero and amortizing + * adjustment residues. By default the adjustment quantum is 1 us for + * the usual Unix tickadj() system call, but this can be increased if + * necessary by a configuration command. For instance, when the + * adjtime() quantum is a clock tick for a 100-Hz clock, the quantum + * should be 10 ms. + */ +double sys_tick = 1e-6; /* tickadj() quantum (s) */ +double sys_residual = 0; /* adjustment residue (s) */ + +#ifndef SIM + +/* + * get_systime - return system time in NTP timestamp format. + */ +void +get_systime( + l_fp *now /* system time */ + ) +{ + double dtemp; + +#if defined(HAVE_CLOCK_GETTIME) || defined(HAVE_GETCLOCK) + struct timespec ts; /* seconds and nanoseconds */ + + /* + * Convert Unix clock from seconds and nanoseconds to seconds. + */ +# ifdef HAVE_CLOCK_GETTIME + clock_gettime(CLOCK_REALTIME, &ts); +# else + getclock(TIMEOFDAY, &ts); +# endif + now->l_i = ts.tv_sec + JAN_1970; + dtemp = ts.tv_nsec / 1e9; + +#else /* HAVE_CLOCK_GETTIME || HAVE_GETCLOCK */ + struct timeval tv; /* seconds and microseconds */ + + /* + * Convert Unix clock from seconds and microseconds to seconds. + */ + GETTIMEOFDAY(&tv, NULL); + now->l_i = tv.tv_sec + JAN_1970; + dtemp = tv.tv_usec / 1e6; + +#endif /* HAVE_CLOCK_GETTIME || HAVE_GETCLOCK */ + + /* + * Renormalize to seconds past 1900 and fraction. + */ + dtemp += sys_residual; + if (dtemp >= 1) { + dtemp -= 1; + now->l_i++; + } else if (dtemp < -1) { + dtemp += 1; + now->l_i--; + } + dtemp *= FRAC; + now->l_uf = (u_int32)dtemp; +} + + +/* + * adj_systime - adjust system time by the argument. + */ +#if !defined SYS_WINNT +int /* 0 okay, 1 error */ +adj_systime( + double now /* adjustment (s) */ + ) +{ + struct timeval adjtv; /* new adjustment */ + struct timeval oadjtv; /* residual adjustment */ + double dtemp; + long ticks; + int isneg = 0; + + /* + * Most Unix adjtime() implementations adjust the system clock + * in microsecond quanta, but some adjust in 10-ms quanta. We + * carefully round the adjustment to the nearest quantum, then + * adjust in quanta and keep the residue for later. + */ + dtemp = now + sys_residual; + if (dtemp < 0) { + isneg = 1; + dtemp = -dtemp; + } + adjtv.tv_sec = (long)dtemp; + dtemp -= adjtv.tv_sec; + ticks = (long)(dtemp / sys_tick + .5); + adjtv.tv_usec = (long)(ticks * sys_tick * 1e6); + dtemp -= adjtv.tv_usec / 1e6; + sys_residual = dtemp; + + /* + * Convert to signed seconds and microseconds for the Unix + * adjtime() system call. Note we purposely lose the adjtime() + * leftover. + */ + if (isneg) { + adjtv.tv_sec = -adjtv.tv_sec; + adjtv.tv_usec = -adjtv.tv_usec; + } + if (adjtime(&adjtv, &oadjtv) < 0) { + msyslog(LOG_ERR, "adj_systime: %m"); + return (0); + } + return (1); +} +#endif + + +/* + * step_systime - step the system clock. + */ +int +step_systime( + double now + ) +{ + struct timeval timetv, adjtv, oldtimetv; + int isneg = 0; + double dtemp; +#if defined(HAVE_CLOCK_GETTIME) || defined(HAVE_GETCLOCK) + struct timespec ts; +#endif + + dtemp = sys_residual + now; + if (dtemp < 0) { + isneg = 1; + dtemp = - dtemp; + adjtv.tv_sec = (int32)dtemp; + adjtv.tv_usec = (u_int32)((dtemp - + (double)adjtv.tv_sec) * 1e6 + .5); + } else { + adjtv.tv_sec = (int32)dtemp; + adjtv.tv_usec = (u_int32)((dtemp - + (double)adjtv.tv_sec) * 1e6 + .5); + } +#if defined(HAVE_CLOCK_GETTIME) || defined(HAVE_GETCLOCK) +#ifdef HAVE_CLOCK_GETTIME + (void) clock_gettime(CLOCK_REALTIME, &ts); +#else + (void) getclock(TIMEOFDAY, &ts); +#endif + 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_systime: step %.6f residual %.6f\n", now, sys_residual); +#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 (ntp_set_tod(&timetv, NULL) != 0) { + msyslog(LOG_ERR, "step-systime: %m"); + return (0); + } + sys_residual = 0; + +#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); +} + +#else /* SIM */ +/* + * Clock routines for the simulator - Harish Nair, with help + */ +/* + * get_systime - return the system time in NTP timestamp format + */ +void +get_systime( + l_fp *now /* current system time in l_fp */ ) +{ + /* + * To fool the code that determines the local clock precision, + * we advance the clock a minimum of 200 nanoseconds on every + * clock read. This is appropriate for a typical modern machine + * with nanosecond clocks. Note we make no attempt here to + * simulate reading error, since the error is so small. This may + * change when the need comes to implement picosecond clocks. + */ + if (ntp_node.ntp_time == ntp_node.last_time) + ntp_node.ntp_time += 200e-9; + ntp_node.last_time = ntp_node.ntp_time; + DTOLFP(ntp_node.ntp_time, now); +} + + +/* + * adj_systime - advance or retard the system clock exactly like the + * real thng. + */ +int /* always succeeds */ +adj_systime( + double now /* time adjustment (s) */ + ) +{ + struct timeval adjtv; /* new adjustment */ + double dtemp; + long ticks; + int isneg = 0; + + /* + * Most Unix adjtime() implementations adjust the system clock + * in microsecond quanta, but some adjust in 10-ms quanta. We + * carefully round the adjustment to the nearest quantum, then + * adjust in quanta and keep the residue for later. + */ + dtemp = now + sys_residual; + if (dtemp < 0) { + isneg = 1; + dtemp = -dtemp; + } + adjtv.tv_sec = (long)dtemp; + dtemp -= adjtv.tv_sec; + ticks = (long)(dtemp / sys_tick + .5); + adjtv.tv_usec = (long)(ticks * sys_tick * 1e6); + dtemp -= adjtv.tv_usec / 1e6; + sys_residual = dtemp; + + /* + * Convert to signed seconds and microseconds for the Unix + * adjtime() system call. Note we purposely lose the adjtime() + * leftover. + */ + if (isneg) { + adjtv.tv_sec = -adjtv.tv_sec; + adjtv.tv_usec = -adjtv.tv_usec; + sys_residual = -sys_residual; + } + + /* + * We went to all the trouble just to be sure the emulation is + * precise. We now return to our regularly scheduled concert. + */ + ntp_node.clk_time -= adjtv.tv_sec + adjtv.tv_usec / 1e6; + return (1); +} + + +/* + * step_systime - step the system clock. We are religious here. + */ +int /* always succeeds */ +step_systime( + double now /* step adjustment (s) */ + ) +{ + ntp_node.adj = now; + return (1); +} + +/* + * node_clock - update the clocks + */ +int /* always succeeds */ +node_clock( + Node *n, /* global node pointer */ + double t /* node time */ + ) +{ + double dtemp; + + /* + * Advance client clock (ntp_time). Advance server clock + * (clk_time) adjusted for systematic and random frequency + * errors. The random error is a random walk computed as the + * integral of samples from a Gaussian distribution. + */ + dtemp = t - n->ntp_time; + n->time = t; + n->ntp_time += dtemp; + n->ferr += gauss(0, dtemp * n->fnse); + n->clk_time += dtemp * (1 + n->ferr); + + /* + * Perform the adjtime() function. If the adjustment completed + * in the previous interval, amortize the entire amount; if not, + * carry the leftover to the next interval. + */ + dtemp *= n->slew; + if (dtemp < fabs(n->adj)) { + if (n->adj < 0) { + n->adj += dtemp; + n->ntp_time -= dtemp; + } else { + n->adj -= dtemp; + n->ntp_time += dtemp; + } + } else { + n->ntp_time += n->adj; + n->adj = 0; + } + return (0); +} + + +/* + * gauss() - returns samples from a gaussion distribution + */ +double /* Gaussian sample */ +gauss( + double m, /* sample mean */ + double s /* sample standard deviation (sigma) */ + ) +{ + double q1, q2; + + /* + * Roll a sample from a Gaussian distribution with mean m and + * standard deviation s. For m = 0, s = 1, mean(y) = 0, + * std(y) = 1. + */ + if (s == 0) + return (m); + while ((q1 = drand48()) == 0); + q2 = drand48(); + return (m + s * sqrt(-2. * log(q1)) * cos(2. * PI * q2)); +} + + +/* + * poisson() - returns samples from a network delay distribution + */ +double /* delay sample (s) */ +poisson( + double m, /* fixed propagation delay (s) */ + double s /* exponential parameter (mu) */ + ) +{ + double q1; + + /* + * Roll a sample from a composite distribution with propagation + * delay m and exponential distribution time with parameter s. + * For m = 0, s = 1, mean(y) = std(y) = 1. + */ + if (s == 0) + return (m); + while ((q1 = drand48()) == 0); + return (m - s * log(q1 * s)); +} +#endif /* SIM */ |