#include <u.h>
#include <libc.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 */
uvlong avgerr;

/* ntp server info */
int stratum = 14;
vlong mydisp, rootdisp;
vlong mydelay, rootdelay;
vlong avgdelay;
vlong lastutc;
uchar rootid[4];
char *sysid;
int myprec;

/* list of time samples */
typedef struct Sample Sample;
struct Sample
{
       Sample  *next;
       uvlong  ticks;
       vlong   ltime;
       vlong   stime;
};

/* ntp packet */
typedef struct NTPpkt NTPpkt;
struct NTPpkt
{
       uchar   mode;
       uchar   stratum;
       uchar   poll;
       uchar   precision;
       uchar   rootdelay[4];
       uchar   rootdisp[4];
       uchar   rootid[4];
       uchar   refts[8];
       uchar   origts[8];      /* departed client */
       uchar   recvts[8];      /* arrived at server */
       uchar   xmitts[8];      /* departed server */
       uchar   keyid[4];
       uchar   digest[16];
};

/* ntp server */
typedef struct NTPserver NTPserver;
struct NTPserver
{
       NTPserver *next;
       char    *name;
       uchar   stratum;
       uchar   precision;
       vlong   rootdelay;
       vlong   rootdisp;
       vlong   rtt;
       vlong   dt;
};

NTPserver *ntpservers;

enum
{
       NTPSIZE=        48,     /* basic ntp packet */
       NTPDIGESTSIZE=  20,     /* key and digest */
};

/* error bound of last sample */
ulong   ε;

static void     addntpserver(char *name);
static int      adjustperiod(vlong diff, vlong accuracy, int secs);
static void     background(void);
static int      caperror(vlong dhz, int tsecs, vlong taccuracy);
static long     fstime(void);
static int      gettime(vlong *nsec, uvlong *ticks, uvlong *hz); /* returns time, ticks, hz */
static int      getclockprecision(vlong);
static vlong    gpssample(void);
static void     hnputts(void *p, vlong nsec);
static void     hnputts(void *p, vlong nsec);
static void     inittime(void);
static vlong    nhgetts(void *p);
static vlong    nhgetts(void *p);
static void     ntpserver(char*);
static vlong    ntpsample(void);
static int      ntptimediff(NTPserver *ns);
static int      openfreqfile(void);
static vlong    readfreqfile(int fd, vlong ohz, vlong minhz, vlong maxhz);
static long     rtctime(void);
static void     setrtctime(long);
static vlong    sample(long (*get)(void));
static void     setpriority(void);
static void     setrootid(char *d);
static void     settime(vlong now, uvlong hz, vlong delta, int n); /* set time, hz, delta, period */
static vlong    utcsample(void);
static uvlong   vabs(vlong);
static uvlong   whatisthefrequencykenneth(uvlong hz, uvlong minhz, uvlong maxhz,
                       vlong dt, vlong ticks, vlong period);
static void     writefreqfile(int fd, vlong hz, int secs, vlong 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;
       uvlong hz, minhz, maxhz, period, nhz;
       vlong 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("sysname");

       /* detach from the current namespace */
       if(debug)
               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 = open(timeserver, ORDWR);
               if(fd < 0)
                       sysfatal("opening %s: %r", timeserver);
               if(amount(fd, "/n/boot", MREPL, "") == -1)
                       sysfatal("mounting %s: %r", timeserver);
               close(fd);
               break;
       case Rtc:
               bind("#r", "/dev", MAFTER);
               if(access("/dev/rtc", AREAD) < 0)
                       sysfatal("accessing /dev/rtc: %r");
               break;
       case Utc:
               fd = open(timeserver, OREAD);
               if(fd < 0)
                       sysfatal("opening %s: %r", timeserver);
               utcfil = fd;
               break;
       case Gps:
               fd = 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(rfork(RFPROC|RFFDG|RFMEM|RFNOWAIT)){
               case -1:
                       sysfatal("forking: %r");
               case 0:
                       ntpserver(servenet[i]);
                       _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 */
               ε = ~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 ε we work off during the
                        * sampling period.
                        */
                       if(abs(diff) > ε)
                               if(diff > 0)
                                       settime(-1, 0, diff-((3*ε)/4), secs);
                               else
                                       settime(-1, 0, diff+((3*ε)/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(vlong diff, vlong accuracy, int secs)
{
       uvlong 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 uvlong
whatisthefrequencykenneth(uvlong hz, uvlong minhz, uvlong maxhz, vlong dt,
       vlong ticks, vlong period)
{
       uvlong 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(vlong dhz, int tsecs, vlong 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", 0) < 0)
               bind("#c", "/dev", MAFTER);
       if(access("/dev/rtc", 0) < 0)
               bind("#r", "/dev", MAFTER);

       /* figure out what interface we have */
       ifc = Ibintime;
       bintimefd = open("/dev/bintime", mode);
       if(bintimefd >= 0)
               return;
       ifc = Insec;
       nsecfd = open("/dev/nsec", mode);
       if(nsecfd < 0)
               sysfatal("opening /dev/nsec");
       fastclockfd = open("/dev/fastclock", mode);
       if(fastclockfd < 0)
               sysfatal("opening /dev/fastclock");
       timingfd = open("/dev/timing", OREAD);
       if(timingfd < 0)
               return;
       ifc = Itiming;
}

/*
*  convert binary numbers from/to kernel
*/
static uvlong uvorder = 0x0001020304050607ULL;

static uchar*
be2vlong(vlong *to, uchar *f)
{
       uchar *t, *o;
       int i;

       t = (uchar*)to;
       o = (uchar*)&uvorder;
       for(i = 0; i < sizeof(vlong); i++)
               t[o[i]] = f[i];
       return f+sizeof(vlong);
}

static uchar*
vlong2be(uchar *t, vlong from)
{
       uchar *f, *o;
       int i;

       f = (uchar*)&from;
       o = (uchar*)&uvorder;
       for(i = 0; i < sizeof(vlong); i++)
               t[i] = f[o[i]];
       return t+sizeof(vlong);
}

static long order = 0x00010203;

static uchar*
long2be(uchar *t, long from)
{
       uchar *f, *o;
       int i;

       f = (uchar*)&from;
       o = (uchar*)&order;
       for(i = 0; i < sizeof(long); i++)
               t[i] = f[o[i]];
       return t+sizeof(long);
}

/*
* read ticks and local time in nanoseconds
*/
static int
gettime(vlong *nsec, uvlong *ticks, uvlong *hz)
{
       int i, n;
       uchar ub[3*8], *p;
       char b[2*24+1];

       switch(ifc){
       case Ibintime:
               n = sizeof(vlong);
               if(hz != nil)
                       n = 3*sizeof(vlong);
               if(ticks != nil)
                       n = 2*sizeof(vlong);
               i = read(bintimefd, ub, n);
               if(i != n)
                       break;
               p = ub;
               if(nsec != nil)
                       be2vlong(nsec, ub);
               p += sizeof(vlong);
               if(ticks != nil)
                       be2vlong((vlong*)ticks, p);
               p += sizeof(vlong);
               if(hz != nil)
                       be2vlong((vlong*)hz, p);
               return 0;
       case Itiming:
               n = sizeof(vlong);
               if(ticks != nil)
                       n = 2*sizeof(vlong);
               i = read(timingfd, ub, n);
               if(i != n)
                       break;
               p = ub;
               if(nsec != nil)
                       be2vlong(nsec, ub);
               p += sizeof(vlong);
               if(ticks != nil)
                       be2vlong((vlong*)ticks, p);
               if(hz != nil){
                       seek(fastclockfd, 0, 0);
                       n = 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){
                       seek(nsecfd, 0, 0);
                       n = read(nsecfd, b, sizeof(b)-1);
                       if(n <= 0)
                               break;
                       b[n] = 0;
                       *nsec = strtoll(b, 0, 0);
               }
               if(ticks != nil){
                       seek(fastclockfd, 0, 0);
                       n = read(fastclockfd, b, sizeof(b)-1);
                       if(n <= 0)
                               break;
                       b[n] = 0;
                       *ticks = strtoll(b, 0, 0);
               }
               if(hz != nil){
                       seek(fastclockfd, 0, 0);
                       n = 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(vlong now, uvlong hz, vlong delta, int n)
{
       uchar b[1+sizeof(vlong)+sizeof(long)], *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(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(write(bintimefd, b, p-b) < 0)
                               sysfatal("writing /dev/bintime: %r");
               }
               if(hz != 0){
                       p = b;
                       *p++ = 'f';
                       p = vlong2be(p, hz);
                       if(write(bintimefd, b, p-b) < 0)
                               sysfatal("writing /dev/bintime: %r");
               }
               break;
       case Itiming:
       case Insec:
               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){
                       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 = open(buf, OWRITE);
       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");
       close(fd);
}

/* convert to ntp timestamps */
static void
hnputts(void *p, vlong nsec)
{
       uchar *a;
       ulong 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 vlong
nhgetts(void *p)
{
       uchar *a;
       ulong tsh, tsl;
       vlong 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, vlong nsec)
{
       uchar *a;
       ulong fp;

       a = p;

       fp = nsec/(SEC/((vlong)(1<<16)));
       hnputl(a, fp);
}

/* convert from ntp fixed point to nanosecs */
static vlong
nhgetfp(void *p)
{
       uchar *a;
       ulong fp;
       vlong nsec;

       a = p;
       fp = nhgetl(a);
       nsec = ((vlong)fp)*(SEC/((vlong)(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 = open(buf, OREAD);
       if(fd < 0)
               return;
       n = read(fd, buf, sizeof buf);
       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)
               noted(NCONT);
       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;
       vlong dt, recvts, origts, xmitts, destts, x;
       char dir[64];
       static int whined;

       notify(ding);
       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++){
               alarm(2*1000);

               gettime(&x, 0, 0);
               hnputts(ntpout.xmitts, x);
               if(write(fd, &ntpout, NTPSIZE) < 0){
                       alarm(0);
                       continue;
               }

               n = read(fd, &ntpin, sizeof ntpin);
               alarm(0);
               gettime(&destts, 0, 0);
               if(n >= NTPSIZE){
                       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);
       }
       close(fd);
       return -1;
}

static vlong
gpssample(void)
{
       vlong   l, g, d;
       int     i, n;
       char    *v[4], buf[128];

       d = -1000000000000000000LL;
       for(i = 0; i < 5; i++){
               sleep(1100);
               seek(gpsfil, 0, 0);
               n = 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 vlong
ntpsample(void)
{
       NTPserver *tns, *ns;
       vlong 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;

       ε = abs(ns->rtt/2);
       return ns->dt;
}

/*
* sample the utc file
*/
static vlong
utcsample(void)
{
       vlong   s;
       int     n;
       char    *v[2], buf[128];

       s = 0;
       seek(utcfil, 0, 0);
       n = 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 = open(data, ORDWR);
       if(fd < 0)
               sysfatal("open %s: %r", data);
       return fd;
}

static void
ntpserver(char *servenet)
{
       int fd, n, vers, mode;
       vlong 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 = 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);
               write(fd, buf, NTPSIZE + Udphdrsize);
       }
}

/*
*  get the current time from the file system
*/
static long
fstime(void)
{
       Dir *d;
       ulong 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 long
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 = open("/dev/rtc", OREAD|OCEXEC);
               if(f < 0)
                       break;
               if(seek(f, 0, 0) < 0 || (i = read(f, b, sizeof b)) < 0){
                       close(f);
                       f = -1;
               } else
                       if(i != 0)
                               break;
       }
       return strtoul(b, 0, 10)+gmtdelta;
}

static void
setrtctime(long t)
{
       static int f = -1;

       if(f < 0)
               f = open("/dev/rtc", OWRITE|OCEXEC);
       if(f < 0)
               return;
       if(seek(f, 0, 0) < 0 || fprint(f, "%ld", t-gmtdelta) < 0){
               close(f);
               f = -1;
       }
}


/*
*  Sample a clock.  We wait for the clock to always
*  be at the leading edge of a clock period.
*/
static vlong
sample(long (*get)(void))
{
       long this, last;
       vlong 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 = open(p, ORDWR);
       if(fd < 0)
               fd = create(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 vlong
readfreqfile(int fd, vlong ohz, vlong minhz, vlong maxhz)
{
       int n;
       char buf[128];
       vlong hz;

       n = 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, vlong hz, int secs, vlong diff)
{
       long now;
       static long last;

       if(fd < 0)
               return;
       now = time(0);
       if(now - last < 10*60)
               return;
       last = now;
       if(seek(fd, 0, 0) < 0)
               return;
       fprint(fd, "%lld %d %d %lld\n", hz, secs, type, diff);
}

static uvlong
vabs(vlong x)
{
       if(x < 0)
               return -x;
       else
               return x;
}

static void
background(void)
{
       static int inbackground;

       if(inbackground)
               return;

       if(!debug)
               switch(rfork(RFPROC|RFFDG|RFNAMEG|RFNOTEG|RFNOWAIT)){
               case -1:
                       sysfatal("forking: %r");
                       break;
               case 0:
                       break;
               default:
                       exits(0);
               }
       inbackground = 1;
}

static int
getclockprecision(vlong hz)
{
       int i;

       i = 8;
       while(hz > 0){
               i--;
               hz >>= 1;
       }
       return i;
}