1867Sache/*
235142Sache * Copyright (C) 1993-1998 by Andrey A. Chernov, Moscow, Russia.
3867Sache * All rights reserved.
4867Sache *
5867Sache * Redistribution and use in source and binary forms, with or without
6867Sache * modification, are permitted provided that the following conditions
7867Sache * are met:
8867Sache * 1. Redistributions of source code must retain the above copyright
9867Sache *    notice, this list of conditions and the following disclaimer.
10867Sache * 2. Redistributions in binary form must reproduce the above copyright
11867Sache *    notice, this list of conditions and the following disclaimer in the
12867Sache *    documentation and/or other materials provided with the distribution.
13867Sache *
141092Sache * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND
15867Sache * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
16867Sache * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
17867Sache * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
18867Sache * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
19867Sache * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
20867Sache * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
21867Sache * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
22867Sache * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
23867Sache * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
24867Sache * SUCH DAMAGE.
25867Sache */
26867Sache
27114589Sobrien#if 0
28867Sache#ifndef lint
2936625Scharnierstatic const char copyright[] =
3015046Sache"@(#)Copyright (C) 1993-1996 by Andrey A. Chernov, Moscow, Russia.\n\
31867Sache All rights reserved.\n";
32867Sache#endif /* not lint */
33114589Sobrien#endif
34114589Sobrien#include <sys/cdefs.h>
35114589Sobrien__FBSDID("$FreeBSD$");
36867Sache
37867Sache/*
387398Sache * Andrey A. Chernov   <ache@astral.msk.su>    Dec 20 1993
39867Sache *
40867Sache * Fix kernel time value if machine run wall CMOS clock
41867Sache * (and /etc/wall_cmos_clock file present)
42867Sache * using zoneinfo rules or direct TZ environment variable set.
43867Sache * Use Joerg Wunsch idea for seconds accurate offset calculation
44867Sache * with Garrett Wollman and Bruce Evans fixes.
45867Sache *
46867Sache */
47867Sache#include <stdio.h>
484107Sache#include <signal.h>
49867Sache#include <stdlib.h>
50867Sache#include <unistd.h>
514048Sache#include <syslog.h>
52867Sache#include <sys/time.h>
532910Sache#include <sys/param.h>
54253750Savg#include <machine/cpu.h>
552910Sache#include <sys/sysctl.h>
56867Sache
57867Sache#include "pathnames.h"
58867Sache
595232Sache/*#define DEBUG*/
6015046Sache
6115046Sache#define True (1)
6215046Sache#define False (0)
6315046Sache#define Unknown (-1)
6415046Sache
654048Sache#define REPORT_PERIOD (30*60)
664048Sache
6792536Simpstatic void fake(int);
6892536Simpstatic void usage(void);
6926540Scharnier
7079747Sddstatic void
7192536Simpfake(int unused __unused)
7279747Sdd{
734107Sache
7479747Sdd	/* Do nothing. */
7579747Sdd}
7679747Sdd
7792536Simpint
7892536Simpmain(int argc, char *argv[])
79867Sache{
8035142Sache	struct tm local;
81867Sache	struct timeval tv, *stv;
82867Sache	struct timezone tz, *stz;
8315046Sache	int kern_offset, wall_clock, disrtcset;
842910Sache	size_t len;
851092Sache	/* Avoid time_t here, can be unsigned long or worse */
8635142Sache	long offset, localsec, diff;
871092Sache	time_t initial_sec, final_sec;
8815046Sache	int ch;
8915046Sache	int initial_isdst = -1, final_isdst;
9015046Sache	int need_restore = False, sleep_mode = False, looping,
9115046Sache	    init = Unknown;
924107Sache	sigset_t mask, emask;
93867Sache
9424359Simp	while ((ch = getopt(argc, argv, "ais")) != -1)
954090Sache		switch((char)ch) {
964090Sache		case 'i':               /* initial call, save offset */
9715046Sache			if (init != Unknown)
9826540Scharnier				usage();
9915046Sache			init = True;
1004090Sache			break;
1014090Sache		case 'a':               /* adjustment call, use saved offset */
10215046Sache			if (init != Unknown)
10326540Scharnier				usage();
10415046Sache			init = False;
1054090Sache			break;
10615046Sache		case 's':
10715046Sache			sleep_mode = True;
10815046Sache			break;
1094090Sache		default:
11026540Scharnier			usage();
1114090Sache		}
11215046Sache	if (init == Unknown)
11326540Scharnier		usage();
11435142Sache
11535142Sache	if (access(_PATH_CLOCK, F_OK) != 0)
11635142Sache		return 0;
11735142Sache
11815046Sache	if (init)
11915046Sache		sleep_mode = True;
1208871Srgrimes
1214107Sache	sigemptyset(&mask);
1224107Sache	sigemptyset(&emask);
1234107Sache	sigaddset(&mask, SIGTERM);
1244107Sache
1254090Sache	openlog("adjkerntz", LOG_PID|LOG_PERROR, LOG_DAEMON);
1264090Sache
1274107Sache	(void) signal(SIGHUP, SIG_IGN);
1284107Sache
129163628Sru	if (init && daemon(0,
130163628Sru#ifdef DEBUG
131163628Sru	    1
132163628Sru#else
133163628Sru	    0
134163628Sru#endif
135163628Sru	    )) {
1364107Sache		syslog(LOG_ERR, "daemon: %m");
1374107Sache		return 1;
1384107Sache	}
1394107Sache
1404090Sacheagain:
1414107Sache	(void) sigprocmask(SIG_BLOCK, &mask, NULL);
1424107Sache	(void) signal(SIGTERM, fake);
1434107Sache
1447398Sache	diff = 0;
1457398Sache	stv = NULL;
1467398Sache	stz = NULL;
14715046Sache	looping = False;
1487398Sache
14915052Sache	wall_clock = (access(_PATH_CLOCK, F_OK) == 0);
15015057Sache	if (init && !sleep_mode) {
15115057Sache		init = False;
15215057Sache		if (!wall_clock)
15315057Sache			return 0;
15415057Sache	}
15515052Sache
156128683Sghelmer	tzset();
157128683Sghelmer
1587398Sache	len = sizeof(kern_offset);
159158444Sphk	if (sysctlbyname("machdep.adjkerntz", &kern_offset, &len, NULL, 0) == -1) {
160158444Sphk		syslog(LOG_ERR, "sysctl(\"machdep.adjkerntz\"): %m");
1617398Sache		return 1;
1627398Sache	}
1637398Sache
1644090Sache/****** Critical section, do all things as fast as possible ******/
1654090Sache
1664090Sache	/* get local CMOS clock and possible kernel offset */
1674090Sache	if (gettimeofday(&tv, &tz)) {
1684090Sache		syslog(LOG_ERR, "gettimeofday: %m");
1693368Sache		return 1;
1703368Sache	}
171867Sache
1724090Sache	/* get the actual local timezone difference */
1734090Sache	initial_sec = tv.tv_sec;
1747398Sache
1757398Sacherecalculate:
1764090Sache	local = *localtime(&initial_sec);
1777398Sache	if (diff == 0)
1787398Sache		initial_isdst = local.tm_isdst;
17935142Sache	local.tm_isdst = initial_isdst;
180867Sache
1814090Sache	/* calculate local CMOS diff from GMT */
182867Sache
1837398Sache	localsec = mktime(&local);
18435142Sache	if (localsec == -1) {
1854090Sache		/*
1864090Sache		 * XXX user can only control local time, and it is
1874090Sache		 * unacceptable to fail here for init.  2:30 am in the
1884090Sache		 * middle of the nonexistent hour means 3:30 am.
1894090Sache		 */
19015046Sache		if (!sleep_mode) {
19135142Sache			syslog(LOG_WARNING,
19235142Sache			"Warning: nonexistent local time, try to run later.");
19315046Sache			syslog(LOG_WARNING, "Giving up.");
19415046Sache			return 1;
19515046Sache		}
19635142Sache		syslog(LOG_WARNING,
19735142Sache			"Warning: nonexistent local time.");
19815046Sache		syslog(LOG_WARNING, "Will retry after %d minutes.",
19915046Sache			REPORT_PERIOD / 60);
2004107Sache		(void) signal(SIGTERM, SIG_DFL);
2014107Sache		(void) sigprocmask(SIG_UNBLOCK, &mask, NULL);
2024090Sache		(void) sleep(REPORT_PERIOD);
2034090Sache		goto again;
2044090Sache	}
20535142Sache	offset = -local.tm_gmtoff;
2065232Sache#ifdef DEBUG
2075232Sache	fprintf(stderr, "Initial offset: %ld secs\n", offset);
2085232Sache#endif
209867Sache
2104090Sache	/* correct the kerneltime for this diffs */
2114090Sache	/* subtract kernel offset, if present, old offset too */
2124090Sache
2134090Sache	diff = offset - tz.tz_minuteswest * 60 - kern_offset;
2144090Sache
2154090Sache	if (diff != 0) {
2165232Sache#ifdef DEBUG
2175232Sache		fprintf(stderr, "Initial diff: %ld secs\n", diff);
2185232Sache#endif
2194090Sache		/* Yet one step for final time */
2204090Sache
2217398Sache		final_sec = initial_sec + diff;
2224090Sache
223882Sache		/* get the actual local timezone difference */
2244090Sache		local = *localtime(&final_sec);
2257398Sache		final_isdst = diff < 0 ? initial_isdst : local.tm_isdst;
2267398Sache		if (diff > 0 && initial_isdst != final_isdst) {
2277398Sache			if (looping)
2287398Sache				goto bad_final;
22915046Sache			looping = True;
2307398Sache			initial_isdst = final_isdst;
2317398Sache			goto recalculate;
2327398Sache		}
23335142Sache		local.tm_isdst =  final_isdst;
234882Sache
2357398Sache		localsec = mktime(&local);
23635142Sache		if (localsec == -1) {
2377398Sache		bad_final:
2381092Sache			/*
2394090Sache			 * XXX as above.  The user has even less control,
2404090Sache			 * but perhaps we never get here.
2411092Sache			 */
24215046Sache			if (!sleep_mode) {
24335142Sache				syslog(LOG_WARNING,
24435142Sache					"Warning: nonexistent final local time, try to run later.");
24515046Sache				syslog(LOG_WARNING, "Giving up.");
24615046Sache				return 1;
24715046Sache			}
24835142Sache			syslog(LOG_WARNING,
24935142Sache				"Warning: nonexistent final local time.");
25015046Sache			syslog(LOG_WARNING, "Will retry after %d minutes.",
25115046Sache				REPORT_PERIOD / 60);
2524107Sache			(void) signal(SIGTERM, SIG_DFL);
2534107Sache			(void) sigprocmask(SIG_UNBLOCK, &mask, NULL);
2544090Sache			(void) sleep(REPORT_PERIOD);
2554090Sache			goto again;
256882Sache		}
25735142Sache		offset = -local.tm_gmtoff;
2585232Sache#ifdef DEBUG
2595232Sache		fprintf(stderr, "Final offset: %ld secs\n", offset);
2605232Sache#endif
261882Sache
262882Sache		/* correct the kerneltime for this diffs */
263882Sache		/* subtract kernel offset, if present, old offset too */
264882Sache
2652910Sache		diff = offset - tz.tz_minuteswest * 60 - kern_offset;
266882Sache
267882Sache		if (diff != 0) {
2685232Sache#ifdef DEBUG
2695232Sache			fprintf(stderr, "Final diff: %ld secs\n", diff);
2705232Sache#endif
27133831Sache			/*
27233831Sache			 * stv is abused as a flag.  The important value
27333831Sache			 * is in `diff'.
27433831Sache			 */
2754090Sache			stv = &tv;
276882Sache		}
2774090Sache	}
278867Sache
2794090Sache	if (tz.tz_dsttime != 0 || tz.tz_minuteswest != 0) {
2804090Sache		tz.tz_dsttime = tz.tz_minuteswest = 0;  /* zone info is garbage */
2814090Sache		stz = &tz;
2824090Sache	}
28315046Sache	if (!wall_clock && stz == NULL)
28415046Sache		stv = NULL;
285867Sache
28615046Sache	/* if init or UTC clock and offset/date will be changed, */
28715046Sache	/* disable RTC modification for a while.                      */
28815046Sache
28915046Sache	if (   (init && stv != NULL)
29015046Sache	    || ((init || !wall_clock) && kern_offset != offset)
29115046Sache	   ) {
2925076Sache		len = sizeof(disrtcset);
293158444Sphk		if (sysctlbyname("machdep.disable_rtc_set", &disrtcset, &len, NULL, 0) == -1) {
294158516Spjd			syslog(LOG_ERR, "sysctl(get: \"machdep.disable_rtc_set\"): %m");
2955076Sache			return 1;
2965076Sache		}
2975076Sache		if (disrtcset == 0) {
2985076Sache			disrtcset = 1;
29915046Sache			need_restore = True;
300158516Spjd			if (sysctlbyname("machdep.disable_rtc_set", NULL, NULL, &disrtcset, len) == -1) {
301158516Spjd				syslog(LOG_ERR, "sysctl(set: \"machdep.disable_rtc_set\"): %m");
3024090Sache				return 1;
3034090Sache			}
3043368Sache		}
3054090Sache	}
3064039Sache
30733831Sache	if (   (init && (stv != NULL || stz != NULL))
30833831Sache	    || (stz != NULL && stv == NULL)
3095076Sache	   ) {
31033831Sache		if (stv != NULL) {
31133831Sache			/*
31233831Sache			 * Get the time again, as close as possible to
31333831Sache			 * adjusting it, to minimise drift.
31433831Sache			 * XXX we'd better not fail between here and
31533831Sache			 * restoring disrtcset, since we don't clean up
31633831Sache			 * anything.
31733831Sache			 */
318239991Sed			(void)gettimeofday(&tv, NULL);
31933831Sache			tv.tv_sec += diff;
32033831Sache			stv = &tv;
32133831Sache		}
32233831Sache		if (settimeofday(stv, stz)) {
32333831Sache			syslog(LOG_ERR, "settimeofday: %m");
32433831Sache			return 1;
32533831Sache		}
3265076Sache	}
3275076Sache
328158444Sphk	/* setting machdep.adjkerntz have a side effect: resettodr(), which */
329158444Sphk	/* can be disabled by machdep.disable_rtc_set, so if init or UTC clock    */
33015046Sache	/* -- don't write RTC, else write RTC.                          */
33115046Sache
3324090Sache	if (kern_offset != offset) {
3334090Sache		kern_offset = offset;
3344090Sache		len = sizeof(kern_offset);
335158444Sphk		if (sysctlbyname("machdep.adjkerntz", NULL, NULL, &kern_offset, len) == -1) {
336158516Spjd			syslog(LOG_ERR, "sysctl(set: \"machdep.adjkerntz\"): %m");
3374090Sache			return 1;
3383368Sache		}
3394090Sache	}
340867Sache
34115052Sache	len = sizeof(wall_clock);
342158444Sphk	if (sysctlbyname("machdep.wall_cmos_clock",  NULL, NULL, &wall_clock, len) == -1) {
343158444Sphk		syslog(LOG_ERR, "sysctl(set: \"machdep.wall_cmos_clock\"): %m");
34415052Sache		return 1;
34515046Sache	}
34615046Sache
3474090Sache	if (need_restore) {
34815046Sache		need_restore = False;
3494090Sache		disrtcset = 0;
3505076Sache		len = sizeof(disrtcset);
351158444Sphk		if (sysctlbyname("machdep.disable_rtc_set", NULL, NULL, &disrtcset, len) == -1) {
352158444Sphk			syslog(LOG_ERR, "sysctl(set: \"machdep.disable_rtc_set\"): %m");
3534090Sache			return 1;
3544039Sache		}
3554090Sache	}
3564039Sache
357867Sache/****** End of critical section ******/
358867Sache
35915046Sache	if (init && wall_clock) {
36015057Sache		sleep_mode = False;
3614107Sache		/* wait for signals and acts like -a */
3624107Sache		(void) sigsuspend(&emask);
3634107Sache		goto again;
3644107Sache	}
3654107Sache
3664090Sache	return 0;
367867Sache}
36826540Scharnier
36926540Scharnierstatic void
37092536Simpusage(void)
37126540Scharnier{
37226540Scharnier	fprintf(stderr, "%s\n%s\n%s\n%s\n",
37326540Scharnier		"usage: adjkerntz -i",
37426540Scharnier		"\t\t(initial call from /etc/rc)",
37526540Scharnier		"       adjkerntz -a [-s]",
37626540Scharnier		"\t\t(adjustment call, -s for sleep/retry mode)");
37726540Scharnier	exit(2);
37826540Scharnier}
379