/*-
 * Copyright (c) 1985, 1993
 *	The Regents of the University of California.  All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. All advertising materials mentioning features or use of this software
 *    must display the following acknowledgement:
 *	This product includes software developed by the University of
 *	California, Berkeley and its contributors.
 * 4. Neither the name of the University nor the names of its contributors
 *    may be used to endorse or promote products derived from this software
 *    without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 */

#ifndef lint
static char sccsid[] = "@(#)correct.c	8.1 (Berkeley) 6/6/93";
#endif /* not lint */

#ifdef sgi
#ident "$Revision: 1.16 $"
#endif

#include "globals.h"
#include <math.h>
#include <sys/types.h>
#include <sys/times.h>
#ifdef sgi
#include <sys/syssgi.h>
#endif /* sgi */

static void adjclock __P((struct timeval *));

/*
 * sends to the slaves the corrections for their clocks after fixing our
 * own
 */
void
correct(avdelta)
	long avdelta;
{
	struct hosttbl *htp;
	int corr;
	struct timeval adjlocal;
	struct tsp to;
	struct tsp *answer;

	mstotvround(&adjlocal, avdelta);

	for (htp = self.l_fwd; htp != &self; htp = htp->l_fwd) {
		if (htp->delta != HOSTDOWN)  {
			corr = avdelta - htp->delta;
/* If the other machine is off in the weeds, set its time directly.
 *	If a slave gets the wrong day, the original code would simply
 *	fix the minutes.  If you fix a network partition, you can get
 *	into such situations.
 */
			if (htp->need_set
			    || corr >= MAXADJ*1000
			    || corr <= -MAXADJ*1000) {
				htp->need_set = 0;
				(void)gettimeofday(&to.tsp_time,0);
				timevaladd(&to.tsp_time, &adjlocal);
				to.tsp_type = TSP_SETTIME;
			} else {
				mstotvround(&to.tsp_time, corr);
				to.tsp_type = TSP_ADJTIME;
			}
			(void)strcpy(to.tsp_name, hostname);
			answer = acksend(&to, &htp->addr, htp->name,
					 TSP_ACK, 0, 0);
			if (!answer) {
				htp->delta = HOSTDOWN;
				syslog(LOG_WARNING,
				       "no reply to time correction from %s",
				       htp->name);
				if (++htp->noanswer >= LOSTHOST) {
					if (trace) {
						fprintf(fd,
					     "purging %s for not answering\n",
							htp->name);
						(void)fflush(fd);
					}
					htp = remmach(htp);
				}
			}
		}
	}

	/*
	 * adjust our own clock now that we are not sending it out
	 */
	adjclock(&adjlocal);
}


static void
adjclock(corr)
	struct timeval *corr;
{
	static int passes = 0;
	static int smoother = 0;
	long delta;			/* adjustment in usec */
	long ndelta;
	struct timeval now;
	struct timeval adj;

	if (!timerisset(corr))
		return;

	adj = *corr;
	if (adj.tv_sec < MAXADJ && adj.tv_sec > - MAXADJ) {
		delta = adj.tv_sec*1000000 + adj.tv_usec;
		/* If the correction is less than the minimum round
		 *	trip time for an ICMP packet, and thus
		 *	less than the likely error in the measurement,
		 *	do not do the entire correction.  Do half
		 *	or a quarter of it.
		 */

		if (delta > -MIN_ROUND*1000
		    && delta < MIN_ROUND*1000) {
			if (smoother <= 4)
				smoother++;
			ndelta = delta >> smoother;
			if (trace)
				fprintf(fd,
					"trimming delta %ld usec to %ld\n",
					delta, ndelta);
			adj.tv_usec = ndelta;
			adj.tv_sec = 0;
		} else if (smoother > 0) {
			smoother--;
		}
		if (0 > adjtime(corr, 0)) {
			syslog(LOG_ERR, "adjtime: %m");
		}
		if (passes > 1
		    && (delta < -BIG_ADJ || delta > BIG_ADJ)) {
			smoother = 0;
			passes = 0;
			syslog(LOG_WARNING,
			       "large time adjustment of %+.3f sec",
			       delta/1000000.0);
		}
	} else {
		syslog(LOG_WARNING,
		       "clock correction %d sec too large to adjust",
		       adj.tv_sec);
		(void) gettimeofday(&now, 0);
		timevaladd(&now, corr);
		if (settimeofday(&now, 0) < 0)
			syslog(LOG_ERR, "settimeofday: %m");
	}

#ifdef sgi
	/* Accumulate the total change, and use it to adjust the basic
	 * clock rate.
	 */
	if (++passes > 2) {
#define F_USEC_PER_SEC	(1000000*1.0)	/* reduce typos */
#define F_NSEC_PER_SEC	(F_USEC_PER_SEC*1000.0)

		extern char *timetrim_fn;
		extern char *timetrim_wpat;
		extern long timetrim;
		extern double tot_adj, hr_adj;	/* totals in nsec */
		extern double tot_ticks, hr_ticks;

		static double nag_tick;
		double cur_ticks, hr_delta_ticks, tot_delta_ticks;
		double tru_tot_adj, tru_hr_adj; /* nsecs of adjustment */
		double tot_trim, hr_trim;   /* nsec/sec */
		struct tms tm;
		FILE *timetrim_st;

		cur_ticks = times(&tm);
		tot_adj += delta*1000.0;
		hr_adj += delta*1000.0;

		tot_delta_ticks = cur_ticks-tot_ticks;
		if (tot_delta_ticks >= 16*SECDAY*CLK_TCK) {
			tot_adj -= rint(tot_adj/16);
			tot_ticks += rint(tot_delta_ticks/16);
			tot_delta_ticks = cur_ticks-tot_ticks;
		}
		hr_delta_ticks = cur_ticks-hr_ticks;

		tru_hr_adj = hr_adj + timetrim*rint(hr_delta_ticks/CLK_TCK);
		tru_tot_adj = (tot_adj
			       + timetrim*rint(tot_delta_ticks/CLK_TCK));

		if (hr_delta_ticks >= SECDAY*CLK_TCK
		    || (tot_delta_ticks < 4*SECDAY*CLK_TCK
			&& hr_delta_ticks >= SECHR*CLK_TCK)
		    || (trace && hr_delta_ticks >= (SECHR/10)*CLK_TCK)) {

			tot_trim = rint(tru_tot_adj*CLK_TCK/tot_delta_ticks);
			hr_trim = rint(tru_hr_adj*CLK_TCK/hr_delta_ticks);

			if (trace
			    || (abs(timetrim - hr_trim) > 100000.0
				&& 0 == timetrim_fn
				&& ((cur_ticks - nag_tick)
				    >= 24*SECDAY*CLK_TCK))) {
				nag_tick = cur_ticks;
				syslog(LOG_NOTICE,
		   "%+.3f/%.2f or %+.3f/%.2f sec/hr; timetrim=%+.0f or %+.0f",
				       tru_tot_adj/F_NSEC_PER_SEC,
				       tot_delta_ticks/(SECHR*CLK_TCK*1.0),
				       tru_hr_adj/F_NSEC_PER_SEC,
				       hr_delta_ticks/(SECHR*CLK_TCK*1.0),
				       tot_trim,
				       hr_trim);
			}

			if (tot_trim < -MAX_TRIM || tot_trim > MAX_TRIM) {
				tot_ticks = hr_ticks;
				tot_adj = hr_adj;
			} else if (0 > syssgi(SGI_SETTIMETRIM,
					      (long)tot_trim)) {
				syslog(LOG_ERR, "SETTIMETRIM(%d): %m",
				       (long)tot_trim);
			} else {
				if (0 != timetrim_fn) {
				    timetrim_st = fopen(timetrim_fn, "w");
				    if (0 == timetrim_st) {
					syslog(LOG_ERR, "fopen(%s): %m",
					       timetrim_fn);
				    } else {
					if (0 > fprintf(timetrim_st,
							timetrim_wpat,
							(long)tot_trim,
							tru_tot_adj,
							tot_delta_ticks)) {
						syslog(LOG_ERR,
						       "fprintf(%s): %m",
						       timetrim_fn);
					}
					(void)fclose(timetrim_st);
				    }
				}

				tot_adj -= ((tot_trim - timetrim)
					    * rint(tot_delta_ticks/CLK_TCK));
				timetrim = tot_trim;
			}

			hr_ticks = cur_ticks;
			hr_adj = 0;
		}
	}
#endif /* sgi */
}


/* adjust the time in a message by the time it
 *	spent in the queue
 */
void
adj_msg_time(msg, now)
	struct tsp *msg;
	struct timeval *now;
{
	msg->tsp_time.tv_sec += (now->tv_sec - from_when.tv_sec);
	msg->tsp_time.tv_usec += (now->tv_usec - from_when.tv_usec);

	while (msg->tsp_time.tv_usec < 0) {
		msg->tsp_time.tv_sec--;
		msg->tsp_time.tv_usec += 1000000;
	}
	while (msg->tsp_time.tv_usec >= 1000000) {
		msg->tsp_time.tv_sec++;
		msg->tsp_time.tv_usec -= 1000000;
	}
}
