jehanne/sys/src/cmd/ip/timesync.c

1418 lines
27 KiB
C

/*
* This file is part of the UCB release of Plan 9. It is subject to the license
* terms in the LICENSE file found in the top-level directory of this
* distribution and at http://akaros.cs.berkeley.edu/files/Plan9License. No
* part of the UCB release of Plan 9, including this file, may be copied,
* modified, propagated, or distributed except according to the terms contained
* in the LICENSE file.
*/
/* Portions of this file are Copyright (C) 2015-2018 Giacomo Tesio <giacomo@tesio.it>
* See /doc/license/gpl-2.0.txt for details about the licensing.
*/
/* Portions of this file are Copyright (C) 9front's team.
* See /doc/license/9front-mit for details about the licensing.
* See http://git.9front.org/plan9front/plan9front/HEAD/info.html for a list of authors.
*/
#include <u.h>
#include <lib9.h>
#include <envvars.h>
#include <auth.h>
#include <ip.h>
#include <mp.h>
/* nanosecond times */
#define SEC 1000000000LL
#define MIN (60LL*SEC)
#define HOUR (60LL*MIN)
#define DAY (24LL*HOUR)
enum {
Fs,
Rtc,
Ntp,
Utc,
Gps,
HZAvgSecs= 3*60, /* target averaging period for frequency in seconds */
MinSampleSecs= 60, /* minimum sampling time in seconds */
};
char *dir = "/tmp"; /* directory sample files live in */
char *logfile = "timesync";
char *timeserver;
char *Rootid;
int utcfil;
int gpsfil;
int debug;
int impotent;
int logging;
int type;
int gmtdelta; /* rtc+gmtdelta = gmt */
uint64_t avgerr;
/* ntp server info */
int stratum = 14;
int64_t mydisp, rootdisp;
int64_t mydelay, rootdelay;
int64_t avgdelay;
int64_t lastutc;
uint8_t rootid[4];
char *sysid;
int myprec;
/* list of time samples */
typedef struct Sample Sample;
struct Sample
{
Sample *next;
uint64_t ticks;
int64_t ltime;
int64_t stime;
};
/* ntp packet */
typedef struct NTPpkt NTPpkt;
struct NTPpkt
{
uint8_t mode;
uint8_t stratum;
uint8_t poll;
uint8_t precision;
uint8_t rootdelay[4];
uint8_t rootdisp[4];
uint8_t rootid[4];
uint8_t refts[8];
uint8_t origts[8]; /* departed client */
uint8_t recvts[8]; /* arrived at server */
uint8_t xmitts[8]; /* departed server */
uint8_t keyid[4];
uint8_t digest[16];
};
/* ntp server */
typedef struct NTPserver NTPserver;
struct NTPserver
{
NTPserver *next;
char *name;
uint8_t stratum;
uint8_t precision;
int64_t rootdelay;
int64_t rootdisp;
int64_t rtt;
int64_t dt;
};
NTPserver *ntpservers;
enum
{
NTPSIZE= 48, /* basic ntp packet */
NTPDIGESTSIZE= 20, /* key and digest */
};
/* error bound of last sample */
uint32_t epsilon;
static void addntpserver(char *name);
static int adjustperiod(int64_t diff, int64_t accuracy, int secs);
static void background(void);
static int caperror(int64_t dhz, int tsecs, int64_t taccuracy);
static int32_t fstime(void);
static int gettime(int64_t *nsec, uint64_t *ticks, uint64_t *hz); /* returns time, ticks, hz */
static int getclockprecision(int64_t);
static int64_t gpssample(void);
static void hnputts(void *p, int64_t nsec);
static void hnputts(void *p, int64_t nsec);
static void inittime(void);
static int64_t nhgetts(void *p);
static int64_t nhgetts(void *p);
static void ntpserver(char*);
static int64_t ntpsample(void);
static int ntptimediff(NTPserver *ns);
static int openfreqfile(void);
static int64_t readfreqfile(int fd, int64_t ohz, int64_t minhz, int64_t maxhz);
static int32_t rtctime(void);
static void setrtctime(int32_t);
static int64_t sample(int32_t (*get)(void));
static void setpriority(void);
static void setrootid(char *d);
static void settime(int64_t now, uint64_t hz, int64_t delta, int n); /* set time, hz, delta, period */
static int64_t utcsample(void);
static uint64_t vabs(int64_t);
static uint64_t whatisthefrequencykenneth(uint64_t hz, uint64_t minhz, uint64_t maxhz,
int64_t dt, int64_t ticks, int64_t period);
static void writefreqfile(int fd, int64_t hz, int secs, int64_t diff);
// ((1970-1900)*365 + 17 /*leap days*/)*24*60*60
#define EPOCHDIFF 2208988800UL
static void
usage(void)
{
fprint(2, "usage: %s [-a accuracy][-d dir][-I rootid][-s net]"
"[-S stratum][-DfGilLnrU] timesource ...\n", argv0);
exits("usage");
}
void
main(int argc, char **argv)
{
int i, t, fd, nservenet;
int secs; /* sampling period */
int tsecs; /* temporary sampling period */
int syncrtc;
uint64_t hz, minhz, maxhz, period, nhz;
int64_t diff, accuracy, taccuracy;
char *servenet[4];
Sample *s, *x, *first, **l;
Tm tl, tg;
type = Fs; /* by default, sync with the file system */
debug = 0;
syncrtc = 1;
accuracy = 1000000LL; /* default accuracy is 1 millisecond */
nservenet = 0;
tsecs = secs = MinSampleSecs;
timeserver = "";
ARGBEGIN{
case 'a':
accuracy = strtoll(EARGF(usage()), 0, 0); /* specified in ns */
if(accuracy <= 1)
sysfatal("bad accuracy specified");
break;
case 'd':
dir = EARGF(usage());
break;
case 'D':
debug = 1;
break;
case 'f':
type = Fs;
stratum = 2;
break;
case 'G':
type = Gps;
stratum = 1;
break;
case 'i':
impotent = 1;
break;
case 'I':
Rootid = EARGF(usage());
break;
case 'l':
logging = 1;
break;
case 'L':
/*
* Assume time source in local time rather than GMT.
* Calculate difference so that rtctime can return GMT.
* This is useful with the rtc on PC's that run Windows
* since Windows keeps the local time in the rtc.
*/
t = time(0);
tl = *localtime(t);
tg = *gmtime(t);
/*
* if the years are different, we're at most a day off,
* so just rewrite
*/
if(tl.year < tg.year){
tg.year--;
tg.yday = tl.yday + 1;
}else if(tl.year > tg.year){
tl.year--;
tl.yday = tg.yday+1;
}
assert(tl.year == tg.year);
tg.sec -= tl.sec;
tg.min -= tl.min;
tg.hour -= tl.hour;
tg.yday -= tl.yday;
gmtdelta = tg.sec+60*(tg.min+60*(tg.hour+tg.yday*24));
assert(abs(gmtdelta) <= 24*60*60);
break;
case 'n':
type = Ntp;
break;
case 'r':
type = Rtc;
stratum = 0;
syncrtc = 0;
break;
case 'U':
type = Utc;
stratum = 1;
break;
case 's':
if(nservenet >= nelem(servenet))
sysfatal("too many networks to serve on");
servenet[nservenet++] = EARGF(usage());
break;
case 'S':
stratum = strtoll(EARGF(usage()), 0, 0);
break;
default:
usage();
}ARGEND;
fmtinstall('E', eipfmt);
fmtinstall('I', eipfmt);
fmtinstall('V', eipfmt);
sysid = getenv(ENV_SYSNAME);
/* detach from the current namespace */
if(debug)
sys_rfork(RFNAMEG);
switch(type){
case Utc:
if(argc > 0)
timeserver = argv[0];
else
sysfatal("bad time source");
break;
case Gps:
if(argc > 0)
timeserver = argv[0];
else
timeserver = "/mnt/gps/time";
break;
case Fs:
if(argc > 0)
timeserver = argv[0];
else
timeserver = "/srv/boot";
break;
case Ntp:
if(argc > 0)
for(i = 0; i < argc; i++)
addntpserver(argv[i]);
else
addntpserver("$ntp");
break;
}
setpriority();
/* figure out our time interface and initial frequency */
inittime();
gettime(0, 0, &hz);
minhz = hz / 2;
maxhz = hz * 2;
myprec = getclockprecision(hz);
/* convert the accuracy from nanoseconds to ticks */
taccuracy = hz*accuracy/SEC;
/*
* bind in clocks
*/
switch(type){
case Fs:
fd = sys_open(timeserver, ORDWR);
if(fd < 0)
sysfatal("opening %s: %r", timeserver);
if(amount(fd, "/n/boot", MREPL, "") < 0)
sysfatal("mounting %s: %r", timeserver);
sys_close(fd);
break;
case Rtc:
sys_bind("#r", "/dev", MAFTER);
if(access("/dev/rtc", AREAD) < 0)
sysfatal("accessing /dev/rtc: %r");
break;
case Utc:
fd = sys_open(timeserver, OREAD);
if(fd < 0)
sysfatal("opening %s: %r", timeserver);
utcfil = fd;
break;
case Gps:
fd = sys_open(timeserver, OREAD);
if(fd < 0)
sysfatal("opening %s: %r", timeserver);
gpsfil = fd;
break;
}
/*
* start a local ntp server(s)
*/
for(i = 0; i < nservenet; i++)
switch(sys_rfork(RFPROC|RFFDG|RFMEM|RFNOWAIT)){
case -1:
sysfatal("forking: %r");
case 0:
ntpserver(servenet[i]);
sys__exits(0);
}
/* get the last known frequency from the file */
fd = openfreqfile();
hz = readfreqfile(fd, hz, minhz, maxhz);
/*
* this is the main loop. it gets a sample, adjusts the
* clock and computes a sleep period until the next loop.
* we balance frequency drift against the length of the
* period to avoid blowing the accuracy limit.
*/
first = nil;
l = &first;
avgerr = accuracy >> 1;
for(;; background(), sleep(tsecs*1000)){
s = mallocz(sizeof *s, 1);
diff = 0;
/* get times for this sample */
epsilon = ~0;
switch(type){
case Fs:
s->stime = sample(fstime);
break;
case Rtc:
s->stime = sample(rtctime);
break;
case Utc:
s->stime = utcsample();
if(s->stime == 0LL){
if(logging)
syslog(0, logfile, "no sample");
free(s);
if (secs > 60 * 15)
tsecs = 60*15;
continue;
}
break;
case Ntp:
diff = ntpsample();
if(diff == 0LL){
if(logging)
syslog(0, logfile, "no sample");
free(s);
if(secs > 60*15)
tsecs = 60*15;
continue;
}
break;
case Gps:
diff = gpssample();
if(diff == 0LL){
if(logging)
syslog(0, logfile, "no sample");
free(s);
if(secs > 60*15)
tsecs = 60*15;
continue;
}
}
/* use fastest method to read local clock and ticks */
gettime(&s->ltime, &s->ticks, 0);
if(type == Ntp || type == Gps)
s->stime = s->ltime + diff;
/* if the sample was bad, ignore it */
if(s->stime < 0){
free(s);
continue;
}
/* reset local time */
diff = s->stime - s->ltime;
if(diff > 10*SEC || diff < -10*SEC){
/* we're way off, just set the time */
secs = MinSampleSecs;
settime(s->stime, 0, 0, 0);
} else {
/* keep a running average of the error. */
avgerr = (avgerr>>1) + (vabs(diff)>>1);
/*
* the time to next sample depends on how good or
* bad we're doing.
*/
tsecs = secs = adjustperiod(diff, accuracy, secs);
/*
* work off the fixed difference. This is done
* by adding a ramp to the clock. Each 100th of a
* second (or so) the kernel will add diff/(4*secs*100)
* to the clock. we only do 1/4 of the difference per
* period to dampen any measurement noise.
*
* any difference greater than epsilon we work off during the
* sampling period.
*/
if(abs(diff) > epsilon)
if(diff > 0)
settime(-1, 0, diff-((3*epsilon)/4), secs);
else
settime(-1, 0, diff+((3*epsilon)/4), secs);
else
settime(-1, 0, diff, 4*secs);
}
if(syncrtc)
setrtctime(s->stime / SEC);
if(debug)
fprint(2, "δ %lld avgδ %lld f %lld\n", diff, avgerr, hz);
/* dump old samples (keep at least one) */
while(first != nil){
if(first->next == nil)
break;
if(s->stime - first->next->stime < DAY)
break;
x = first;
first = first->next;
free(x);
}
/*
* The sampling error is limited by the total error. If
* we make sure the sampling period is at least 16 million
* times the average error, we should calculate a frequency
* with on average a 1e-7 error.
*
* So that big hz changes don't blow our accuracy requirement,
* we shorten the period to make sure that δhz*secs will be
* greater than the accuracy limit.
*/
period = avgerr << 24;
for(x = first; x != nil; x = x->next)
if(s->stime - x->stime < period ||
x->next == nil || s->stime - x->next->stime < period)
break;
if(x != nil){
nhz = whatisthefrequencykenneth(
hz, minhz, maxhz,
s->stime - x->stime,
s->ticks - x->ticks,
period);
tsecs = caperror(vabs(nhz-hz), tsecs, taccuracy);
hz = nhz;
writefreqfile(fd, hz, (s->stime - x->stime)/SEC, diff);
}
/* add current sample to list. */
*l = s;
l = &s->next;
if(logging)
syslog(0, logfile, "δ %lld avgδ %lld hz %lld",
diff, avgerr, hz);
}
}
/*
* adjust the sampling period with some histeresis
*/
static int
adjustperiod(int64_t diff, int64_t accuracy, int secs)
{
uint64_t absdiff;
absdiff = vabs(diff);
if(absdiff < (accuracy>>1))
secs += 60;
else if(absdiff > accuracy)
secs >>= 1;
else
secs -= 60;
if(secs < MinSampleSecs)
secs = MinSampleSecs;
return secs;
}
/*
* adjust the frequency
*/
static uint64_t
whatisthefrequencykenneth(uint64_t hz, uint64_t minhz, uint64_t maxhz, int64_t dt,
int64_t ticks, int64_t period)
{
uint64_t ohz = hz;
static mpint *mpdt, *mpticks, *mphz, *mpbillion;
/* sanity check */
if(dt <= 0 || ticks <= 0)
return hz;
if(mphz == nil){
mphz = mpnew(0);
mpbillion = uvtomp(SEC, nil);
}
/* hz = (ticks*SEC)/dt */
mpdt = vtomp(dt, mpdt);
mpticks = vtomp(ticks, mpticks);
mpmul(mpticks, mpbillion, mpticks);
mpdiv(mpticks, mpdt, mphz, nil);
hz = mptoui(mphz);
/* sanity */
if(hz < minhz || hz > maxhz)
return ohz;
/* damp the change if we're shorter than the target period */
if(period > dt)
hz = (12ULL*ohz + 4ULL*hz)/16ULL;
settime(-1, hz, 0, 0);
return hz;
}
/*
* We may be changing the frequency to match a bad measurement
* or to match a condition no longer in effect. To make sure
* that this doesn't blow our error budget over the next measurement
* period, shorten the period to make sure that δhz*secs will be
* less than the accuracy limit. Here taccuracy is accuracy converted
* from nanoseconds to ticks.
*/
static int
caperror(int64_t dhz, int tsecs, int64_t taccuracy)
{
if(dhz*tsecs <= taccuracy)
return tsecs;
if(debug)
fprint(2, "δhz %lld tsecs %d tacc %lld\n", dhz, tsecs, taccuracy);
tsecs = taccuracy/dhz;
if(tsecs < MinSampleSecs)
tsecs = MinSampleSecs;
return tsecs;
}
/*
* kernel interface
*/
enum
{
Ibintime,
Insec,
Itiming,
};
int ifc;
int bintimefd = -1;
int timingfd = -1;
int nsecfd = -1;
int fastclockfd = -1;
static void
inittime(void)
{
int mode;
if(impotent)
mode = OREAD;
else
mode = ORDWR;
/* bind in clocks */
if(access("/dev/time", AEXIST) < 0)
sys_bind("#c", "/dev", MAFTER);
if(access("/dev/rtc", AEXIST) < 0)
sys_bind("#r", "/dev", MAFTER);
/* figure out what interface we have */
ifc = Ibintime;
bintimefd = sys_open("/dev/bintime", mode);
if(bintimefd >= 0)
return;
ifc = Insec;
nsecfd = sys_open("/dev/nsec", mode);
if(nsecfd < 0)
sysfatal("opening /dev/nsec");
fastclockfd = sys_open("/dev/fastclock", mode);
if(fastclockfd < 0)
sysfatal("opening /dev/fastclock");
timingfd = sys_open("/dev/timing", OREAD);
if(timingfd < 0)
return;
ifc = Itiming;
}
/*
* convert binary numbers from/to kernel
*/
static uint64_t uvorder = 0x0001020304050607ULL;
static uint8_t*
be2vlong(int64_t *to, uint8_t *f)
{
uint8_t *t, *o;
int i;
t = (uint8_t*)to;
o = (uint8_t*)&uvorder;
for(i = 0; i < sizeof(int64_t); i++)
t[o[i]] = f[i];
return f+sizeof(int64_t);
}
static uint8_t*
vlong2be(uint8_t *t, int64_t from)
{
uint8_t *f, *o;
int i;
f = (uint8_t*)&from;
o = (uint8_t*)&uvorder;
for(i = 0; i < sizeof(int64_t); i++)
t[i] = f[o[i]];
return t+sizeof(int64_t);
}
static int32_t order = 0x00010203;
/*
static uint8_t*
be2long(int32_t *to, uint8_t *f)
{
uint8_t *t, *o;
int i;
t = (uint8_t*)to;
o = (uint8_t*)&order;
for(i = 0; i < sizeof(int32_t); i++)
t[o[i]] = f[i];
return f+sizeof(int32_t);
}
*/
static uint8_t*
long2be(uint8_t *t, int32_t from)
{
uint8_t *f, *o;
int i;
f = (uint8_t*)&from;
o = (uint8_t*)&order;
for(i = 0; i < sizeof(int32_t); i++)
t[i] = f[o[i]];
return t+sizeof(int32_t);
}
/*
* read ticks and local time in nanoseconds
*/
static int
gettime(int64_t *nsec, uint64_t *ticks, uint64_t *hz)
{
int i, n;
uint8_t ub[3*8], *p;
char b[2*24+1];
switch(ifc){
case Ibintime:
n = sizeof(int64_t);
if(hz != nil)
n = 3*sizeof(int64_t);
if(ticks != nil)
n = 2*sizeof(int64_t);
i = jehanne_read(bintimefd, ub, n);
if(i != n)
break;
p = ub;
if(nsec != nil)
be2vlong(nsec, ub);
p += sizeof(int64_t);
if(ticks != nil)
be2vlong((int64_t*)ticks, p);
p += sizeof(int64_t);
if(hz != nil)
be2vlong((int64_t*)hz, p);
return 0;
case Itiming:
n = sizeof(int64_t);
if(ticks != nil)
n = 2*sizeof(int64_t);
i = jehanne_read(timingfd, ub, n);
if(i != n)
break;
p = ub;
if(nsec != nil)
be2vlong(nsec, ub);
p += sizeof(int64_t);
if(ticks != nil)
be2vlong((int64_t*)ticks, p);
if(hz != nil){
sys_seek(fastclockfd, 0, 0);
n = jehanne_read(fastclockfd, b, sizeof(b)-1);
if(n <= 0)
break;
b[n] = 0;
*hz = strtoll(b+24, 0, 0);
}
return 0;
case Insec:
if(nsec != nil){
sys_seek(nsecfd, 0, 0);
n = jehanne_read(nsecfd, b, sizeof(b)-1);
if(n <= 0)
break;
b[n] = 0;
*nsec = strtoll(b, 0, 0);
}
if(ticks != nil){
sys_seek(fastclockfd, 0, 0);
n = jehanne_read(fastclockfd, b, sizeof(b)-1);
if(n <= 0)
break;
b[n] = 0;
*ticks = strtoll(b, 0, 0);
}
if(hz != nil){
sys_seek(fastclockfd, 0, 0);
n = jehanne_read(fastclockfd, b, sizeof(b)-1);
if(n <= 24)
break;
b[n] = 0;
*hz = strtoll(b+24, 0, 0);
}
return 0;
}
return -1;
}
static void
settime(int64_t now, uint64_t hz, int64_t delta, int n)
{
uint8_t b[1+sizeof(int64_t)+sizeof(int32_t)], *p;
if(debug)
fprint(2, "settime(now=%lld, hz=%llud, delta=%lld, period=%d)\n",
now, hz, delta, n);
if(impotent)
return;
switch(ifc){
case Ibintime:
if(now >= 0){
p = b;
*p++ = 'n';
p = vlong2be(p, now);
if(jehanne_write(bintimefd, b, p-b) < 0)
sysfatal("writing /dev/bintime: %r");
}
if(delta != 0){
p = b;
*p++ = 'd';
p = vlong2be(p, delta);
p = long2be(p, n);
if(jehanne_write(bintimefd, b, p-b) < 0)
sysfatal("writing /dev/bintime: %r");
}
if(hz != 0){
p = b;
*p++ = 'f';
p = vlong2be(p, hz);
if(jehanne_write(bintimefd, b, p-b) < 0)
sysfatal("writing /dev/bintime: %r");
}
break;
case Itiming:
case Insec:
sys_seek(nsecfd, 0, 0);
if(now >= 0 || delta != 0){
if(fprint(nsecfd, "%lld %lld %d", now, delta, n) < 0)
sysfatal("writing /dev/nsec: %r");
}
if(hz > 0){
sys_seek(fastclockfd, 0, 0);
if(fprint(fastclockfd, "%lld", hz) < 0)
sysfatal("writing /dev/fastclock: %r");
}
}
}
/*
* set priority high and wire process to a processor
*/
static void
setpriority(void)
{
int fd;
char buf[32];
sprint(buf, "/proc/%d/ctl", getpid());
fd = sys_open(buf, ORDWR);
if(fd < 0){
fprint(2, "can't set priority\n");
return;
}
if(fprint(fd, "pri 100") < 0)
fprint(2, "can't set priority\n");
if(fprint(fd, "wired 2") < 0)
fprint(2, "can't wire process\n");
sys_close(fd);
}
/* convert to ntp timestamps */
static void
hnputts(void *p, int64_t nsec)
{
uint8_t *a;
uint32_t tsh, tsl;
a = p;
/* zero is a special case */
if(nsec == 0)
return;
tsh = nsec/SEC;
nsec -= tsh*SEC;
tsl = (nsec<<32)/SEC;
hnputl(a, tsh+EPOCHDIFF);
hnputl(a+4, tsl);
}
/* convert from ntp timestamps */
static int64_t
nhgetts(void *p)
{
uint8_t *a;
uint32_t tsh, tsl;
int64_t nsec;
a = p;
tsh = nhgetl(a);
tsl = nhgetl(a+4);
nsec = tsl*SEC;
nsec >>= 32;
nsec += (tsh - EPOCHDIFF)*SEC;
return nsec;
}
/* convert to ntp 32 bit fixed point */
static void
hnputfp(void *p, int64_t nsec)
{
uint8_t *a;
uint32_t fp;
a = p;
fp = nsec/(SEC/((int64_t)(1<<16)));
hnputl(a, fp);
}
/* convert from ntp fixed point to nanosecs */
static int64_t
nhgetfp(void *p)
{
uint8_t *a;
uint32_t fp;
int64_t nsec;
a = p;
fp = nhgetl(a);
nsec = ((int64_t)fp)*(SEC/((int64_t)(1<<16)));
return nsec;
}
/* get network address of the server */
static void
setrootid(char *d)
{
char buf[128];
int fd, n;
char *p;
snprint(buf, sizeof buf, "%s/remote", d);
fd = sys_open(buf, OREAD);
if(fd < 0)
return;
n = jehanne_read(fd, buf, sizeof buf);
sys_close(fd);
if(n <= 0)
return;
p = strchr(buf, '!');
if(p != nil)
*p = 0;
v4parseip(rootid, buf);
}
static void
ding(void* _, char *s)
{
if(strstr(s, "alarm") != nil)
sys_noted(NCONT);
sys_noted(NDFLT);
}
static void
addntpserver(char *name)
{
NTPserver *ns, **l;
ns = mallocz(sizeof(NTPserver), 1);
if(ns == nil)
sysfatal("addntpserver: %r");
timeserver = strdup(name);
ns->name = name;
for(l = &ntpservers; *l != nil; l = &(*l)->next)
;
*l = ns;
}
/*
* sntp client, we keep calling if the delay seems
* unusually high, i.e., 30% longer than avg.
*/
static int
ntptimediff(NTPserver *ns)
{
int fd, tries, n;
NTPpkt ntpin, ntpout;
int64_t dt, recvts, origts, xmitts, destts, x;
char dir[64];
static int whined;
sys_notify(ding);
sys_alarm(30*1000); /* don't wait forever if ns->name is unreachable */
fd = dial(netmkaddr(ns->name, "udp", "ntp"), 0, dir, 0);
if(fd < 0){
if (!whined++)
syslog(0, logfile, "can't reach %s: %r", ns->name);
return -1;
}
setrootid(dir);
memset(&ntpout, 0, sizeof(ntpout));
ntpout.mode = 3 | (3 << 3);
for(tries = 0; tries < 3; tries++){
sys_alarm(2*1000);
gettime(&x, 0, 0);
hnputts(ntpout.xmitts, x);
if(jehanne_write(fd, &ntpout, NTPSIZE) < 0){
sys_alarm(0);
continue;
}
n = jehanne_read(fd, &ntpin, sizeof ntpin);
sys_alarm(0);
gettime(&destts, 0, 0);
if(n >= NTPSIZE){
sys_close(fd);
/* we got one, use it */
recvts = nhgetts(ntpin.recvts);
origts = nhgetts(ntpin.origts);
xmitts = nhgetts(ntpin.xmitts);
dt = ((recvts - origts) + (xmitts - destts))/2;
/* save results */
ns->rtt = ((destts - origts) - (xmitts - recvts))/2;
ns->dt = dt;
ns->stratum = ntpin.stratum;
ns->precision = ntpin.precision;
ns->rootdelay = nhgetfp(ntpin.rootdelay);
ns->rootdisp = nhgetfp(ntpin.rootdisp);
if(debug)
fprint(2, "ntp %s stratum %d ntpdelay(%lld)\n",
ns->name, ntpin.stratum, ns->rtt);
return 0;
}
/* try again */
sleep(250);
}
sys_close(fd);
return -1;
}
static int64_t
gpssample(void)
{
int64_t l, g, d;
int i, n;
char *v[4], buf[128];
d = -1000000000000000000LL;
for(i = 0; i < 5; i++){
sleep(1100);
sys_seek(gpsfil, 0, 0);
n = jehanne_read(gpsfil, buf, sizeof buf - 1);
if (n <= 0)
return 0;
buf[n] = 0;
n = tokenize(buf, v, nelem(v));
if(n != 4 || strcmp(v[3], "A") != 0)
return 0;
g = atoll(v[1]);
l = atoll(v[2]);
if(g-l > d)
d = g-l;
}
return d;
}
static int64_t
ntpsample(void)
{
NTPserver *tns, *ns;
int64_t metric, x;
metric = 1000LL*SEC;
ns = nil;
for(tns = ntpservers; tns != nil; tns = tns->next){
if(ntptimediff(tns) < 0)
continue;
x = vabs(tns->rootdisp) + (vabs(tns->rtt+tns->rootdelay)>>1);
if(debug)
fprint(2, "ntp %s rootdelay %lld rootdisp %lld metric %lld\n",
tns->name, tns->rootdelay, tns->rootdisp, x);
if(x < metric){
metric = x;
ns = tns;
}
}
if(ns == nil)
return 0;
/* save data for our server */
rootdisp = ns->rootdisp;
rootdelay = ns->rootdelay;
mydelay = ns->rtt;
mydisp = avgerr;
if(ns->stratum == 0)
stratum = 0;
else
stratum = ns->stratum + 1;
epsilon = abs(ns->rtt/2);
return ns->dt;
}
/*
* sample the utc file
*/
static int64_t
utcsample(void)
{
int64_t s;
int n;
char *v[2], buf[128];
s = 0;
sys_seek(utcfil, 0, 0);
n = jehanne_read(utcfil, buf, sizeof buf - 1);
if (n <= 0)
return 0;
buf[n] = 0;
n = tokenize(buf, v, nelem(v));
if (strcmp(v[0], "0") == 0)
return 0;
if (n == 2) {
gettime(&s, nil, nil);
s -= atoll(v[1]);
}
lastutc = atoll(v[0]) + s;
return lastutc;
}
/*
* sntp server
*/
static int
openlisten(char *net)
{
int fd, cfd;
char data[128], devdir[40];
sprint(data, "%s/udp!*!ntp", net);
cfd = announce(data, devdir);
if(cfd < 0)
sysfatal("can't announce");
if(fprint(cfd, "headers") < 0)
sysfatal("can't set header mode");
sprint(data, "%s/data", devdir);
fd = sys_open(data, ORDWR);
if(fd < 0)
sysfatal("open %s: %r", data);
return fd;
}
static void
ntpserver(char *servenet)
{
int fd, n, vers, mode;
int64_t recvts, x;
char buf[512];
NTPpkt *ntp;
fd = openlisten(servenet);
if (Rootid == nil)
switch(type){
case Fs:
Rootid = "WWV";
break;
case Rtc:
Rootid = "LOCL";
break;
case Utc:
Rootid = "UTC";
break;
case Gps:
Rootid = "GPS";
break;
case Ntp:
/* set by the ntp client */
break;
}
if (Rootid != nil)
memmove(rootid, Rootid, strlen(Rootid) > 4? 4: strlen(Rootid));
for(;;){
n = jehanne_read(fd, buf, sizeof buf);
gettime(&recvts, 0, 0);
if(n <= 0) {
/* don't croak on input error, but don't spin either */
sleep(500);
continue;
}
if(n < Udphdrsize + NTPSIZE)
continue;
ntp = (NTPpkt*)(buf + Udphdrsize);
mode = ntp->mode & 7;
vers = (ntp->mode>>3) & 7;
if(mode != 3)
continue;
ntp->mode = (vers<<3)|4;
ntp->stratum = stratum;
ntp->precision = myprec;
hnputfp(ntp->rootdelay, rootdelay + mydelay);
hnputfp(ntp->rootdisp, rootdisp + mydisp);
hnputts(ntp->refts, lastutc);
memmove(ntp->origts, ntp->xmitts, sizeof(ntp->origts));
hnputts(ntp->recvts, recvts);
memmove(ntp->rootid, rootid, sizeof(ntp->rootid));
gettime(&x, 0, 0);
hnputts(ntp->xmitts, x);
jehanne_write(fd, buf, NTPSIZE + Udphdrsize);
}
}
/*
* get the current time from the file system
*/
static int32_t
fstime(void)
{
Dir *d;
uint32_t t;
d = dirstat("/n/boot");
if(d != nil){
t = d->atime;
free(d);
} else
t = 0;
return t;
}
/*
* get the current time from the real time clock
*/
static int32_t
rtctime(void)
{
char b[20];
static int f = -1;
int i, retries;
memset(b, 0, sizeof(b));
for(retries = 0; retries < 100; retries++){
if(f < 0)
f = sys_open("/dev/rtc", OREAD|OCEXEC);
if(f < 0)
break;
if(sys_seek(f, 0, 0) < 0 || (i = jehanne_read(f, b, sizeof b)) < 0){
sys_close(f);
f = -1;
} else
if(i != 0)
break;
}
return strtoul(b, 0, 10)+gmtdelta;
}
static void
setrtctime(int32_t t)
{
static int f = -1;
if(f < 0)
f = sys_open("/dev/rtc", OWRITE|OCEXEC);
if(f < 0)
return;
if(sys_seek(f, 0, 0) < 0 || fprint(f, "%ld", t-gmtdelta) < 0){
sys_close(f);
f = -1;
}
}
/*
* Sample a clock. We wait for the clock to always
* be at the leading edge of a clock period.
*/
static int64_t
sample(int32_t (*get)(void))
{
int32_t this, last;
int64_t start, end;
/*
* wait for the second to change
*/
last = (*get)();
for(;;){
gettime(&start, 0, 0);
sleep(5);
this = (*get)();
gettime(&end, 0, 0);
if(this != last)
break;
last = this;
}
return SEC*this - (end-start)/2;
}
/*
* the name of the frequency file has the method and possibly the
* server name encoded in it.
*/
static int
openfreqfile(void)
{
char *p;
int fd;
if(sysid == nil)
return -1;
switch(type){
case Ntp:
p = smprint("%s/ts.%s.%d.%s", dir, sysid, type, timeserver);
break;
default:
p = smprint("%s/ts.%s.%d", dir, sysid, type);
break;
}
fd = sys_open(p, ORDWR);
if(fd < 0)
fd = ocreate(p, ORDWR, 0666);
free(p);
if(fd < 0)
return -1;
return fd;
}
/*
* the file contains the last known frequency and the
* number of seconds it was sampled over
*/
static int64_t
readfreqfile(int fd, int64_t ohz, int64_t minhz, int64_t maxhz)
{
int n;
char buf[128];
int64_t hz;
n = jehanne_read(fd, buf, sizeof buf-1);
if(n <= 0)
return ohz;
buf[n] = 0;
hz = strtoll(buf, nil, 0);
if(hz > maxhz || hz < minhz)
return ohz;
settime(-1, hz, 0, 0);
return hz;
}
/*
* remember hz and averaging period
*/
static void
writefreqfile(int fd, int64_t hz, int secs, int64_t diff)
{
int32_t now;
static int32_t last;
if(fd < 0)
return;
now = time(0);
if(now - last < 10*60)
return;
last = now;
if(sys_seek(fd, 0, 0) < 0)
return;
fprint(fd, "%lld %d %d %lld\n", hz, secs, type, diff);
}
static uint64_t
vabs(int64_t x)
{
if(x < 0)
return -x;
else
return x;
}
static void
background(void)
{
static int inbackground;
if(inbackground)
return;
if(!debug)
switch(sys_rfork(RFPROC|RFFDG|RFNAMEG|RFNOTEG|RFNOWAIT)){
case -1:
sysfatal("forking: %r");
break;
case 0:
break;
default:
exits(0);
}
inbackground = 1;
}
static int
getclockprecision(int64_t hz)
{
int i;
i = 8;
while(hz > 0){
i--;
hz >>= 1;
}
return i;
}