adjkerntz.c revision 24359
121308Sache/*
221308Sache * Copyright (C) 1993-1996 by Andrey A. Chernov, Moscow, Russia.
321308Sache * All rights reserved.
421308Sache *
521308Sache * Redistribution and use in source and binary forms, with or without
621308Sache * modification, are permitted provided that the following conditions
721308Sache * are met:
821308Sache * 1. Redistributions of source code must retain the above copyright
921308Sache *    notice, this list of conditions and the following disclaimer.
1021308Sache * 2. Redistributions in binary form must reproduce the above copyright
1121308Sache *    notice, this list of conditions and the following disclaimer in the
1221308Sache *    documentation and/or other materials provided with the distribution.
1321308Sache *
1421308Sache * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND
1521308Sache * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
1621308Sache * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
1721308Sache * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
1821308Sache * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
1921308Sache * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
2021308Sache * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
2121308Sache * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
2221308Sache * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
2321308Sache * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
2421308Sache * SUCH DAMAGE.
2521308Sache */
2621308Sache
2721308Sache#ifndef lint
2821308Sachechar copyright[] =
2921308Sache"@(#)Copyright (C) 1993-1996 by Andrey A. Chernov, Moscow, Russia.\n\
3021308Sache All rights reserved.\n";
3121308Sache#endif /* not lint */
3221308Sache
3321308Sache/*
3421308Sache * Andrey A. Chernov   <ache@astral.msk.su>    Dec 20 1993
3521308Sache *
3621308Sache * Fix kernel time value if machine run wall CMOS clock
3721308Sache * (and /etc/wall_cmos_clock file present)
3821308Sache * using zoneinfo rules or direct TZ environment variable set.
3921308Sache * Use Joerg Wunsch idea for seconds accurate offset calculation
4021308Sache * with Garrett Wollman and Bruce Evans fixes.
4121308Sache *
4221308Sache */
4321308Sache#include <stdio.h>
4421308Sache#include <signal.h>
4521308Sache#include <stdlib.h>
4621308Sache#include <unistd.h>
4721308Sache#include <syslog.h>
4821308Sache#include <sys/stat.h>
4921308Sache#include <sys/time.h>
5021308Sache#include <sys/param.h>
5121308Sache#include <machine/cpu.h>
5221308Sache#include <sys/sysctl.h>
5321308Sache
5421308Sache#include "pathnames.h"
5521308Sache
5621308Sache/*#define DEBUG*/
5721308Sache
5821308Sache#define True (1)
5921308Sache#define False (0)
6021308Sache#define Unknown (-1)
6121308Sache
6221308Sache#define REPORT_PERIOD (30*60)
6321308Sache
6421308Sachevoid fake() {}
6521308Sache
6621308Sacheint main(argc, argv)
6721308Sache	int argc;
6821308Sache	char **argv;
6921308Sache{
7021308Sache	struct tm local, utc;
7121308Sache	struct timeval tv, *stv;
7221308Sache	struct timezone tz, *stz;
7321308Sache	int kern_offset, wall_clock, disrtcset;
7421308Sache	size_t len;
7521308Sache	int mib[2];
7621308Sache	/* Avoid time_t here, can be unsigned long or worse */
7721308Sache	long offset, utcsec, localsec, diff;
7821308Sache	time_t initial_sec, final_sec;
7921308Sache	int ch;
8021308Sache	int initial_isdst = -1, final_isdst;
8121308Sache	int need_restore = False, sleep_mode = False, looping,
8221308Sache	    init = Unknown;
8321308Sache	sigset_t mask, emask;
8421308Sache
8521308Sache	while ((ch = getopt(argc, argv, "ais")) != -1)
8621308Sache		switch((char)ch) {
8721308Sache		case 'i':               /* initial call, save offset */
8821308Sache			if (init != Unknown)
8921308Sache				goto usage;
9021308Sache			init = True;
9121308Sache			break;
9221308Sache		case 'a':               /* adjustment call, use saved offset */
9321308Sache			if (init != Unknown)
9421308Sache				goto usage;
9521308Sache			init = False;
9621308Sache			break;
9721308Sache		case 's':
9821308Sache			sleep_mode = True;
9921308Sache			break;
10021308Sache		default:
10121308Sache		usage:
10221308Sache			fprintf(stderr, "Usage:\n\
10321308Sache\tadjkerntz -i\t\t(initial call from /etc/rc)\n\
10421308Sache\tadjkerntz -a [-s]\t(adjustment call, -s for sleep/retry mode)\n");
10521308Sache  			return 2;
10621308Sache		}
10721308Sache	if (init == Unknown)
10821308Sache		goto usage;
10921308Sache	if (init)
11021308Sache		sleep_mode = True;
11121308Sache
11221308Sache	sigemptyset(&mask);
11321308Sache	sigemptyset(&emask);
11421308Sache	sigaddset(&mask, SIGTERM);
11521308Sache
11621308Sache	openlog("adjkerntz", LOG_PID|LOG_PERROR, LOG_DAEMON);
11721308Sache
11821308Sache	(void) signal(SIGHUP, SIG_IGN);
11921308Sache
12021308Sache	if (init && daemon(0, 1)) {
12121308Sache		syslog(LOG_ERR, "daemon: %m");
12221308Sache		return 1;
12321308Sache	}
12421308Sache
12521308Sacheagain:
12621308Sache	(void) sigprocmask(SIG_BLOCK, &mask, NULL);
12721308Sache	(void) signal(SIGTERM, fake);
12821308Sache
12921308Sache	diff = 0;
13021308Sache	stv = NULL;
13121308Sache	stz = NULL;
13221308Sache	looping = False;
13321308Sache
13421308Sache	wall_clock = (access(_PATH_CLOCK, F_OK) == 0);
13521308Sache	if (init && !sleep_mode) {
13621308Sache		init = False;
13721308Sache		if (!wall_clock)
13821308Sache			return 0;
13921308Sache	}
14021308Sache
14121308Sache	mib[0] = CTL_MACHDEP;
14221308Sache	mib[1] = CPU_ADJKERNTZ;
14321308Sache	len = sizeof(kern_offset);
14421308Sache	if (sysctl(mib, 2, &kern_offset, &len, NULL, 0) == -1) {
14521308Sache		syslog(LOG_ERR, "sysctl(get_offset): %m");
14621308Sache		return 1;
14721308Sache	}
14821308Sache
14921308Sache/****** Critical section, do all things as fast as possible ******/
15021308Sache
15121308Sache	/* get local CMOS clock and possible kernel offset */
15221308Sache	if (gettimeofday(&tv, &tz)) {
15321308Sache		syslog(LOG_ERR, "gettimeofday: %m");
15421308Sache		return 1;
15521308Sache	}
15621308Sache
15721308Sache	/* get the actual local timezone difference */
15821308Sache	initial_sec = tv.tv_sec;
15921308Sache
16021308Sacherecalculate:
16121308Sache	local = *localtime(&initial_sec);
16221308Sache	if (diff == 0)
16321308Sache		initial_isdst = local.tm_isdst;
16421308Sache	utc = *gmtime(&initial_sec);
16521308Sache	local.tm_isdst = utc.tm_isdst = initial_isdst;
16621308Sache
16721308Sache	/* calculate local CMOS diff from GMT */
16821308Sache
16921308Sache	utcsec = mktime(&utc);
17021308Sache	localsec = mktime(&local);
17121308Sache	if (utcsec == -1 || localsec == -1) {
17221308Sache		/*
17321308Sache		 * XXX user can only control local time, and it is
17421308Sache		 * unacceptable to fail here for init.  2:30 am in the
17521308Sache		 * middle of the nonexistent hour means 3:30 am.
17621308Sache		 */
17721308Sache		syslog(LOG_WARNING,
17821308Sache		"Warning: nonexistent %s time.",
17921308Sache			utcsec == -1 && localsec == -1 ? "UTC time and local" :
18021308Sache			utcsec == -1 ? "UTC" : "local");
18121308Sache		if (!sleep_mode) {
18221308Sache			syslog(LOG_WARNING, "Giving up.");
18321308Sache			return 1;
18421308Sache		}
18521308Sache		syslog(LOG_WARNING, "Will retry after %d minutes.",
18621308Sache			REPORT_PERIOD / 60);
18721308Sache		(void) signal(SIGTERM, SIG_DFL);
18821308Sache		(void) sigprocmask(SIG_UNBLOCK, &mask, NULL);
18921308Sache		(void) sleep(REPORT_PERIOD);
19021308Sache		goto again;
19121308Sache	}
19221308Sache	offset = utcsec - localsec;
19321308Sache#ifdef DEBUG
19421308Sache	fprintf(stderr, "Initial offset: %ld secs\n", offset);
19521308Sache#endif
19621308Sache
19721308Sache	/* correct the kerneltime for this diffs */
19821308Sache	/* subtract kernel offset, if present, old offset too */
19921308Sache
20021308Sache	diff = offset - tz.tz_minuteswest * 60 - kern_offset;
20121308Sache
20221308Sache	if (diff != 0) {
20321308Sache#ifdef DEBUG
20421308Sache		fprintf(stderr, "Initial diff: %ld secs\n", diff);
20521308Sache#endif
20621308Sache		/* Yet one step for final time */
20721308Sache
20821308Sache		final_sec = initial_sec + diff;
20921308Sache
21021308Sache		/* get the actual local timezone difference */
21121308Sache		local = *localtime(&final_sec);
21221308Sache		final_isdst = diff < 0 ? initial_isdst : local.tm_isdst;
21321308Sache		if (diff > 0 && initial_isdst != final_isdst) {
21421308Sache			if (looping)
21521308Sache				goto bad_final;
21621308Sache			looping = True;
21721308Sache			initial_isdst = final_isdst;
21821308Sache			goto recalculate;
21921308Sache		}
22021308Sache		utc = *gmtime(&final_sec);
22121308Sache		local.tm_isdst = utc.tm_isdst = final_isdst;
22221308Sache
22321308Sache		utcsec = mktime(&utc);
22421308Sache		localsec = mktime(&local);
22521308Sache		if (utcsec == -1 || localsec == -1) {
22621308Sache		bad_final:
22721308Sache			/*
22821308Sache			 * XXX as above.  The user has even less control,
22921308Sache			 * but perhaps we never get here.
23021308Sache			 */
23121308Sache			syslog(LOG_WARNING,
23221308Sache				"Warning: nonexistent final %s time.",
23321308Sache				utcsec == -1 && localsec == -1 ? "UTC time and local" :
23421308Sache				utcsec == -1 ? "UTC" : "local");
23521308Sache			if (!sleep_mode) {
23621308Sache				syslog(LOG_WARNING, "Giving up.");
23721308Sache				return 1;
23821308Sache			}
23921308Sache			syslog(LOG_WARNING, "Will retry after %d minutes.",
24021308Sache				REPORT_PERIOD / 60);
24121308Sache			(void) signal(SIGTERM, SIG_DFL);
24221308Sache			(void) sigprocmask(SIG_UNBLOCK, &mask, NULL);
24321308Sache			(void) sleep(REPORT_PERIOD);
24421308Sache			goto again;
24521308Sache		}
24621308Sache		offset = utcsec - localsec;
24721308Sache#ifdef DEBUG
24821308Sache		fprintf(stderr, "Final offset: %ld secs\n", offset);
24921308Sache#endif
25021308Sache
25121308Sache		/* correct the kerneltime for this diffs */
25221308Sache		/* subtract kernel offset, if present, old offset too */
25321308Sache
25421308Sache		diff = offset - tz.tz_minuteswest * 60 - kern_offset;
25521308Sache
25621308Sache		if (diff != 0) {
25721308Sache#ifdef DEBUG
25821308Sache			fprintf(stderr, "Final diff: %ld secs\n", diff);
25921308Sache#endif
26021308Sache			tv.tv_sec += diff;
26121308Sache			tv.tv_usec = 0;       /* we are restarting here... */
26221308Sache			stv = &tv;
26321308Sache		}
26421308Sache	}
26521308Sache
26621308Sache	if (tz.tz_dsttime != 0 || tz.tz_minuteswest != 0) {
26721308Sache		tz.tz_dsttime = tz.tz_minuteswest = 0;  /* zone info is garbage */
26821308Sache		stz = &tz;
26921308Sache	}
27021308Sache	if (!wall_clock && stz == NULL)
27121308Sache		stv = NULL;
27221308Sache
27321308Sache	/* if init or UTC clock and offset/date will be changed, */
27421308Sache	/* disable RTC modification for a while.                      */
27521308Sache
27621308Sache	if (   (init && stv != NULL)
27721308Sache	    || ((init || !wall_clock) && kern_offset != offset)
27821308Sache	   ) {
27921308Sache		mib[0] = CTL_MACHDEP;
28021308Sache		mib[1] = CPU_DISRTCSET;
28121308Sache		len = sizeof(disrtcset);
28221308Sache		if (sysctl(mib, 2, &disrtcset, &len, NULL, 0) == -1) {
28321308Sache			syslog(LOG_ERR, "sysctl(get_disrtcset): %m");
28421308Sache			return 1;
28521308Sache		}
28621308Sache		if (disrtcset == 0) {
28721308Sache			disrtcset = 1;
28821308Sache			need_restore = True;
28921308Sache			if (sysctl(mib, 2, NULL, NULL, &disrtcset, len) == -1) {
29021308Sache				syslog(LOG_ERR, "sysctl(set_disrtcset): %m");
29121308Sache				return 1;
29221308Sache			}
29321308Sache		}
29421308Sache	}
29521308Sache
29621308Sache	if (   (   (init && (stv != NULL || stz != NULL))
29721308Sache		|| (stz != NULL && stv == NULL)
29821308Sache	       )
29921308Sache	    && settimeofday(stv, stz)
30021308Sache	   ) {
30121308Sache		syslog(LOG_ERR, "settimeofday: %m");
30221308Sache		return 1;
30321308Sache	}
30421308Sache
30521308Sache	/* setting CPU_ADJKERNTZ have a side effect: resettodr(), which */
30621308Sache	/* can be disabled by CPU_DISRTCSET, so if init or UTC clock    */
30721308Sache	/* -- don't write RTC, else write RTC.                          */
30821308Sache
30921308Sache	if (kern_offset != offset) {
31021308Sache		kern_offset = offset;
31121308Sache		mib[0] = CTL_MACHDEP;
31221308Sache		mib[1] = CPU_ADJKERNTZ;
31321308Sache		len = sizeof(kern_offset);
31421308Sache		if (sysctl(mib, 2, NULL, NULL, &kern_offset, len) == -1) {
31521308Sache			syslog(LOG_ERR, "sysctl(update_offset): %m");
31621308Sache			return 1;
31721308Sache		}
31821308Sache	}
31921308Sache
32021308Sache	mib[0] = CTL_MACHDEP;
32121308Sache	mib[1] = CPU_WALLCLOCK;
32221308Sache	len = sizeof(wall_clock);
32321308Sache	if (sysctl(mib, 2, NULL, NULL, &wall_clock, len) == -1) {
32421308Sache		syslog(LOG_ERR, "sysctl(put_wallclock): %m");
32521308Sache		return 1;
32621308Sache	}
32721308Sache
32821308Sache	if (need_restore) {
32921308Sache		need_restore = False;
33021308Sache		mib[0] = CTL_MACHDEP;
33121308Sache		mib[1] = CPU_DISRTCSET;
33221308Sache		disrtcset = 0;
33321308Sache		len = sizeof(disrtcset);
33421308Sache		if (sysctl(mib, 2, NULL, NULL, &disrtcset, len) == -1) {
33521308Sache			syslog(LOG_ERR, "sysctl(restore_disrtcset): %m");
33621308Sache			return 1;
33721308Sache		}
33821308Sache	}
33921308Sache
34021308Sache/****** End of critical section ******/
34121308Sache
34221308Sache	if (init && wall_clock) {
34321308Sache		sleep_mode = False;
34421308Sache		/* wait for signals and acts like -a */
34521308Sache		(void) sigsuspend(&emask);
34621308Sache		goto again;
34721308Sache	}
34821308Sache
34921308Sache	return 0;
35021308Sache}
35121308Sache