adjkerntz.c revision 15046
1867Sache/*
215046Sache * Copyright (C) 1993-1996 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
27867Sache#ifndef lint
28867Sachechar copyright[] =
2915046Sache"@(#)Copyright (C) 1993-1996 by Andrey A. Chernov, Moscow, Russia.\n\
30867Sache All rights reserved.\n";
31867Sache#endif /* not lint */
32867Sache
33867Sache/*
347398Sache * Andrey A. Chernov   <ache@astral.msk.su>    Dec 20 1993
35867Sache *
36867Sache * Fix kernel time value if machine run wall CMOS clock
37867Sache * (and /etc/wall_cmos_clock file present)
38867Sache * using zoneinfo rules or direct TZ environment variable set.
39867Sache * Use Joerg Wunsch idea for seconds accurate offset calculation
40867Sache * with Garrett Wollman and Bruce Evans fixes.
41867Sache *
42867Sache */
43867Sache#include <stdio.h>
444107Sache#include <signal.h>
45867Sache#include <stdlib.h>
46867Sache#include <unistd.h>
474048Sache#include <syslog.h>
48867Sache#include <sys/stat.h>
49867Sache#include <sys/time.h>
502910Sache#include <sys/param.h>
512910Sache#include <machine/cpu.h>
522910Sache#include <sys/sysctl.h>
53867Sache
54867Sache#include "pathnames.h"
55867Sache
565232Sache/*#define DEBUG*/
5715046Sache
5815046Sache#define True (1)
5915046Sache#define False (0)
6015046Sache#define Unknown (-1)
6115046Sache
624048Sache#define REPORT_PERIOD (30*60)
634048Sache
644107Sachevoid fake() {}
654107Sache
66867Sacheint main(argc, argv)
67867Sache	int argc;
68867Sache	char **argv;
69867Sache{
70867Sache	struct tm local, utc;
71867Sache	struct timeval tv, *stv;
72867Sache	struct timezone tz, *stz;
7315046Sache	int kern_offset, wall_clock, disrtcset;
742910Sache	size_t len;
752910Sache	int mib[2];
761092Sache	/* Avoid time_t here, can be unsigned long or worse */
772910Sache	long offset, utcsec, localsec, diff;
781092Sache	time_t initial_sec, final_sec;
7915046Sache	int ch;
8015046Sache	int initial_isdst = -1, final_isdst;
8115046Sache	int need_restore = False, sleep_mode = False, looping,
8215046Sache	    init = Unknown;
834107Sache	sigset_t mask, emask;
84867Sache
8515046Sache	while ((ch = getopt(argc, argv, "ais")) != EOF)
864090Sache		switch((char)ch) {
874090Sache		case 'i':               /* initial call, save offset */
8815046Sache			if (init != Unknown)
894090Sache				goto usage;
9015046Sache			init = True;
914090Sache			break;
924090Sache		case 'a':               /* adjustment call, use saved offset */
9315046Sache			if (init != Unknown)
944090Sache				goto usage;
9515046Sache			init = False;
964090Sache			break;
9715046Sache		case 's':
9815046Sache			sleep_mode = True;
9915046Sache			break;
1004090Sache		default:
1014090Sache		usage:
1024090Sache			fprintf(stderr, "Usage:\n\
10315046Sache\tadjkerntz -i\t\t(initial call from /etc/rc)\n\
10415046Sache\tadjkerntz -a [-s]\t(adjustment call, -s for sleep/retry mode)\n");
1054090Sache  			return 2;
1064090Sache		}
10715046Sache	if (init == Unknown)
1084090Sache		goto usage;
10915046Sache	if (init)
11015046Sache		sleep_mode = True;
1118871Srgrimes
1124107Sache	sigemptyset(&mask);
1134107Sache	sigemptyset(&emask);
1144107Sache	sigaddset(&mask, SIGTERM);
1154107Sache
1164090Sache	openlog("adjkerntz", LOG_PID|LOG_PERROR, LOG_DAEMON);
1174090Sache
1184107Sache	(void) signal(SIGHUP, SIG_IGN);
1194107Sache
1204107Sache	if (init && daemon(0, 1)) {
1214107Sache		syslog(LOG_ERR, "daemon: %m");
1224107Sache		return 1;
1234107Sache	}
1244107Sache
12515046Sache	if (init)
12615046Sache		wall_clock = (access(_PATH_CLOCK, F_OK) == 0);
12715046Sache	else {
12815046Sache		mib[0] = CTL_MACHDEP;
12915046Sache		mib[1] = CPU_WALLCLOCK;
13015046Sache		len = sizeof(wall_clock);
13115046Sache		if (sysctl(mib, 2, &wall_clock, &len, NULL, 0) == -1) {
13215046Sache			syslog(LOG_ERR, "sysctl(get_wallclock): %m");
13315046Sache			return 1;
13415046Sache		}
13515046Sache	}
13615046Sache
1374090Sacheagain:
1384107Sache	(void) sigprocmask(SIG_BLOCK, &mask, NULL);
1394107Sache	(void) signal(SIGTERM, fake);
1404107Sache
1417398Sache	diff = 0;
1427398Sache	stv = NULL;
1437398Sache	stz = NULL;
14415046Sache	looping = False;
1457398Sache
1467398Sache	mib[0] = CTL_MACHDEP;
1477398Sache	mib[1] = CPU_ADJKERNTZ;
1487398Sache	len = sizeof(kern_offset);
1497398Sache	if (sysctl(mib, 2, &kern_offset, &len, NULL, 0) == -1) {
1507398Sache		syslog(LOG_ERR, "sysctl(get_offset): %m");
1517398Sache		return 1;
1527398Sache	}
1537398Sache
1544090Sache/****** Critical section, do all things as fast as possible ******/
1554090Sache
1564090Sache	/* get local CMOS clock and possible kernel offset */
1574090Sache	if (gettimeofday(&tv, &tz)) {
1584090Sache		syslog(LOG_ERR, "gettimeofday: %m");
1593368Sache		return 1;
1603368Sache	}
161867Sache
1624090Sache	/* get the actual local timezone difference */
1634090Sache	initial_sec = tv.tv_sec;
1647398Sache
1657398Sacherecalculate:
1664090Sache	local = *localtime(&initial_sec);
1677398Sache	if (diff == 0)
1687398Sache		initial_isdst = local.tm_isdst;
1694090Sache	utc = *gmtime(&initial_sec);
1707398Sache	local.tm_isdst = utc.tm_isdst = initial_isdst;
171867Sache
1724090Sache	/* calculate local CMOS diff from GMT */
173867Sache
1747398Sache	utcsec = mktime(&utc);
1757398Sache	localsec = mktime(&local);
1764090Sache	if (utcsec == -1 || localsec == -1) {
1774090Sache		/*
1784090Sache		 * XXX user can only control local time, and it is
1794090Sache		 * unacceptable to fail here for init.  2:30 am in the
1804090Sache		 * middle of the nonexistent hour means 3:30 am.
1814090Sache		 */
1824090Sache		syslog(LOG_WARNING,
18315046Sache		"Warning: nonexistent %s time.",
18415046Sache			utcsec == -1 && localsec == -1 ? "UTC time and local" :
18515046Sache			utcsec == -1 ? "UTC" : "local");
18615046Sache		if (!sleep_mode) {
18715046Sache			syslog(LOG_WARNING, "Giving up.");
18815046Sache			return 1;
18915046Sache		}
19015046Sache		syslog(LOG_WARNING, "Will retry after %d minutes.",
19115046Sache			REPORT_PERIOD / 60);
1924107Sache		(void) signal(SIGTERM, SIG_DFL);
1934107Sache		(void) sigprocmask(SIG_UNBLOCK, &mask, NULL);
1944090Sache		(void) sleep(REPORT_PERIOD);
1954090Sache		goto again;
1964090Sache	}
1974090Sache	offset = utcsec - localsec;
1985232Sache#ifdef DEBUG
1995232Sache	fprintf(stderr, "Initial offset: %ld secs\n", offset);
2005232Sache#endif
201867Sache
2024090Sache	/* correct the kerneltime for this diffs */
2034090Sache	/* subtract kernel offset, if present, old offset too */
2044090Sache
2054090Sache	diff = offset - tz.tz_minuteswest * 60 - kern_offset;
2064090Sache
2074090Sache	if (diff != 0) {
2085232Sache#ifdef DEBUG
2095232Sache		fprintf(stderr, "Initial diff: %ld secs\n", diff);
2105232Sache#endif
2114090Sache		/* Yet one step for final time */
2124090Sache
2137398Sache		final_sec = initial_sec + diff;
2144090Sache
215882Sache		/* get the actual local timezone difference */
2164090Sache		local = *localtime(&final_sec);
2177398Sache		final_isdst = diff < 0 ? initial_isdst : local.tm_isdst;
2187398Sache		if (diff > 0 && initial_isdst != final_isdst) {
2197398Sache			if (looping)
2207398Sache				goto bad_final;
22115046Sache			looping = True;
2227398Sache			initial_isdst = final_isdst;
2237398Sache			goto recalculate;
2247398Sache		}
2254090Sache		utc = *gmtime(&final_sec);
2267398Sache		local.tm_isdst = utc.tm_isdst = final_isdst;
227882Sache
2287398Sache		utcsec = mktime(&utc);
2297398Sache		localsec = mktime(&local);
230882Sache		if (utcsec == -1 || localsec == -1) {
2317398Sache		bad_final:
2321092Sache			/*
2334090Sache			 * XXX as above.  The user has even less control,
2344090Sache			 * but perhaps we never get here.
2351092Sache			 */
2364090Sache			syslog(LOG_WARNING,
23715046Sache				"Warning: nonexistent final %s time.",
23815046Sache				utcsec == -1 && localsec == -1 ? "UTC time and local" :
23915046Sache				utcsec == -1 ? "UTC" : "local");
24015046Sache			if (!sleep_mode) {
24115046Sache				syslog(LOG_WARNING, "Giving up.");
24215046Sache				return 1;
24315046Sache			}
24415046Sache			syslog(LOG_WARNING, "Will retry after %d minutes.",
24515046Sache				REPORT_PERIOD / 60);
2464107Sache			(void) signal(SIGTERM, SIG_DFL);
2474107Sache			(void) sigprocmask(SIG_UNBLOCK, &mask, NULL);
2484090Sache			(void) sleep(REPORT_PERIOD);
2494090Sache			goto again;
250882Sache		}
251882Sache		offset = utcsec - localsec;
2525232Sache#ifdef DEBUG
2535232Sache		fprintf(stderr, "Final offset: %ld secs\n", offset);
2545232Sache#endif
255882Sache
256882Sache		/* correct the kerneltime for this diffs */
257882Sache		/* subtract kernel offset, if present, old offset too */
258882Sache
2592910Sache		diff = offset - tz.tz_minuteswest * 60 - kern_offset;
260882Sache
261882Sache		if (diff != 0) {
2625232Sache#ifdef DEBUG
2635232Sache			fprintf(stderr, "Final diff: %ld secs\n", diff);
2645232Sache#endif
2654090Sache			tv.tv_sec += diff;
2664090Sache			tv.tv_usec = 0;       /* we are restarting here... */
2674090Sache			stv = &tv;
268882Sache		}
2694090Sache	}
270867Sache
2714090Sache	if (tz.tz_dsttime != 0 || tz.tz_minuteswest != 0) {
2724090Sache		tz.tz_dsttime = tz.tz_minuteswest = 0;  /* zone info is garbage */
2734090Sache		stz = &tz;
2744090Sache	}
27515046Sache	if (!wall_clock && stz == NULL)
27615046Sache		stv = NULL;
277867Sache
27815046Sache	/* if init or UTC clock and offset/date will be changed, */
27915046Sache	/* disable RTC modification for a while.                      */
28015046Sache
28115046Sache	if (   (init && stv != NULL)
28215046Sache	    || ((init || !wall_clock) && kern_offset != offset)
28315046Sache	   ) {
2845076Sache		mib[0] = CTL_MACHDEP;
2855076Sache		mib[1] = CPU_DISRTCSET;
2865076Sache		len = sizeof(disrtcset);
2875076Sache		if (sysctl(mib, 2, &disrtcset, &len, NULL, 0) == -1) {
2885076Sache			syslog(LOG_ERR, "sysctl(get_disrtcset): %m");
2895076Sache			return 1;
2905076Sache		}
2915076Sache		if (disrtcset == 0) {
2925076Sache			disrtcset = 1;
29315046Sache			need_restore = True;
2945076Sache			if (sysctl(mib, 2, NULL, NULL, &disrtcset, len) == -1) {
2955076Sache				syslog(LOG_ERR, "sysctl(set_disrtcset): %m");
2964090Sache				return 1;
2974090Sache			}
2983368Sache		}
2994090Sache	}
3004039Sache
30115046Sache	if (   (   (init && (stv != NULL || stz != NULL))
30215046Sache		|| (stz != NULL && stv == NULL)
30315046Sache	       )
3045076Sache	    && settimeofday(stv, stz)
3055076Sache	   ) {
3065076Sache		syslog(LOG_ERR, "settimeofday: %m");
3075076Sache		return 1;
3085076Sache	}
3095076Sache
31015046Sache	/* setting CPU_ADJKERNTZ have a side effect: resettodr(), which */
31115046Sache	/* can be disabled by CPU_DISRTCSET, so if init or UTC clock    */
31215046Sache	/* -- don't write RTC, else write RTC.                          */
31315046Sache
3144090Sache	if (kern_offset != offset) {
3154090Sache		kern_offset = offset;
3164090Sache		mib[0] = CTL_MACHDEP;
3174090Sache		mib[1] = CPU_ADJKERNTZ;
3184090Sache		len = sizeof(kern_offset);
3194090Sache		if (sysctl(mib, 2, NULL, NULL, &kern_offset, len) == -1) {
3204090Sache			syslog(LOG_ERR, "sysctl(update_offset): %m");
3214090Sache			return 1;
3223368Sache		}
3234090Sache	}
324867Sache
32515046Sache	if (init) {
32615046Sache		mib[0] = CTL_MACHDEP;
32715046Sache		mib[1] = CPU_WALLCLOCK;
32815046Sache		len = sizeof(wall_clock);
32915046Sache		if (sysctl(mib, 2, NULL, NULL, &wall_clock, len) == -1) {
33015046Sache			syslog(LOG_ERR, "sysctl(put_wallclock): %m");
33115046Sache			return 1;
33215046Sache		}
33315046Sache	}
33415046Sache
3354090Sache	if (need_restore) {
33615046Sache		need_restore = False;
3375078Sache		mib[0] = CTL_MACHDEP;
3385078Sache		mib[1] = CPU_DISRTCSET;
3394090Sache		disrtcset = 0;
3405076Sache		len = sizeof(disrtcset);
3414090Sache		if (sysctl(mib, 2, NULL, NULL, &disrtcset, len) == -1) {
3424090Sache			syslog(LOG_ERR, "sysctl(restore_disrtcset): %m");
3434090Sache			return 1;
3444039Sache		}
3454090Sache	}
3464039Sache
347867Sache/****** End of critical section ******/
348867Sache
34915046Sache	if (init && wall_clock) {
35015046Sache		init = False;
3514107Sache		/* wait for signals and acts like -a */
3524107Sache		(void) sigsuspend(&emask);
3534107Sache		goto again;
3544107Sache	}
3554107Sache
3564090Sache	return 0;
357867Sache}
358