adjkerntz.c revision 15057
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
1254090Sacheagain:
1264107Sache	(void) sigprocmask(SIG_BLOCK, &mask, NULL);
1274107Sache	(void) signal(SIGTERM, fake);
1284107Sache
1297398Sache	diff = 0;
1307398Sache	stv = NULL;
1317398Sache	stz = NULL;
13215046Sache	looping = False;
1337398Sache
13415052Sache	wall_clock = (access(_PATH_CLOCK, F_OK) == 0);
13515057Sache	if (init && !sleep_mode) {
13615057Sache		init = False;
13715057Sache		if (!wall_clock)
13815057Sache			return 0;
13915057Sache	}
14015052Sache
1417398Sache	mib[0] = CTL_MACHDEP;
1427398Sache	mib[1] = CPU_ADJKERNTZ;
1437398Sache	len = sizeof(kern_offset);
1447398Sache	if (sysctl(mib, 2, &kern_offset, &len, NULL, 0) == -1) {
1457398Sache		syslog(LOG_ERR, "sysctl(get_offset): %m");
1467398Sache		return 1;
1477398Sache	}
1487398Sache
1494090Sache/****** Critical section, do all things as fast as possible ******/
1504090Sache
1514090Sache	/* get local CMOS clock and possible kernel offset */
1524090Sache	if (gettimeofday(&tv, &tz)) {
1534090Sache		syslog(LOG_ERR, "gettimeofday: %m");
1543368Sache		return 1;
1553368Sache	}
156867Sache
1574090Sache	/* get the actual local timezone difference */
1584090Sache	initial_sec = tv.tv_sec;
1597398Sache
1607398Sacherecalculate:
1614090Sache	local = *localtime(&initial_sec);
1627398Sache	if (diff == 0)
1637398Sache		initial_isdst = local.tm_isdst;
1644090Sache	utc = *gmtime(&initial_sec);
1657398Sache	local.tm_isdst = utc.tm_isdst = initial_isdst;
166867Sache
1674090Sache	/* calculate local CMOS diff from GMT */
168867Sache
1697398Sache	utcsec = mktime(&utc);
1707398Sache	localsec = mktime(&local);
1714090Sache	if (utcsec == -1 || localsec == -1) {
1724090Sache		/*
1734090Sache		 * XXX user can only control local time, and it is
1744090Sache		 * unacceptable to fail here for init.  2:30 am in the
1754090Sache		 * middle of the nonexistent hour means 3:30 am.
1764090Sache		 */
1774090Sache		syslog(LOG_WARNING,
17815046Sache		"Warning: nonexistent %s time.",
17915046Sache			utcsec == -1 && localsec == -1 ? "UTC time and local" :
18015046Sache			utcsec == -1 ? "UTC" : "local");
18115046Sache		if (!sleep_mode) {
18215046Sache			syslog(LOG_WARNING, "Giving up.");
18315046Sache			return 1;
18415046Sache		}
18515046Sache		syslog(LOG_WARNING, "Will retry after %d minutes.",
18615046Sache			REPORT_PERIOD / 60);
1874107Sache		(void) signal(SIGTERM, SIG_DFL);
1884107Sache		(void) sigprocmask(SIG_UNBLOCK, &mask, NULL);
1894090Sache		(void) sleep(REPORT_PERIOD);
1904090Sache		goto again;
1914090Sache	}
1924090Sache	offset = utcsec - localsec;
1935232Sache#ifdef DEBUG
1945232Sache	fprintf(stderr, "Initial offset: %ld secs\n", offset);
1955232Sache#endif
196867Sache
1974090Sache	/* correct the kerneltime for this diffs */
1984090Sache	/* subtract kernel offset, if present, old offset too */
1994090Sache
2004090Sache	diff = offset - tz.tz_minuteswest * 60 - kern_offset;
2014090Sache
2024090Sache	if (diff != 0) {
2035232Sache#ifdef DEBUG
2045232Sache		fprintf(stderr, "Initial diff: %ld secs\n", diff);
2055232Sache#endif
2064090Sache		/* Yet one step for final time */
2074090Sache
2087398Sache		final_sec = initial_sec + diff;
2094090Sache
210882Sache		/* get the actual local timezone difference */
2114090Sache		local = *localtime(&final_sec);
2127398Sache		final_isdst = diff < 0 ? initial_isdst : local.tm_isdst;
2137398Sache		if (diff > 0 && initial_isdst != final_isdst) {
2147398Sache			if (looping)
2157398Sache				goto bad_final;
21615046Sache			looping = True;
2177398Sache			initial_isdst = final_isdst;
2187398Sache			goto recalculate;
2197398Sache		}
2204090Sache		utc = *gmtime(&final_sec);
2217398Sache		local.tm_isdst = utc.tm_isdst = final_isdst;
222882Sache
2237398Sache		utcsec = mktime(&utc);
2247398Sache		localsec = mktime(&local);
225882Sache		if (utcsec == -1 || localsec == -1) {
2267398Sache		bad_final:
2271092Sache			/*
2284090Sache			 * XXX as above.  The user has even less control,
2294090Sache			 * but perhaps we never get here.
2301092Sache			 */
2314090Sache			syslog(LOG_WARNING,
23215046Sache				"Warning: nonexistent final %s time.",
23315046Sache				utcsec == -1 && localsec == -1 ? "UTC time and local" :
23415046Sache				utcsec == -1 ? "UTC" : "local");
23515046Sache			if (!sleep_mode) {
23615046Sache				syslog(LOG_WARNING, "Giving up.");
23715046Sache				return 1;
23815046Sache			}
23915046Sache			syslog(LOG_WARNING, "Will retry after %d minutes.",
24015046Sache				REPORT_PERIOD / 60);
2414107Sache			(void) signal(SIGTERM, SIG_DFL);
2424107Sache			(void) sigprocmask(SIG_UNBLOCK, &mask, NULL);
2434090Sache			(void) sleep(REPORT_PERIOD);
2444090Sache			goto again;
245882Sache		}
246882Sache		offset = utcsec - localsec;
2475232Sache#ifdef DEBUG
2485232Sache		fprintf(stderr, "Final offset: %ld secs\n", offset);
2495232Sache#endif
250882Sache
251882Sache		/* correct the kerneltime for this diffs */
252882Sache		/* subtract kernel offset, if present, old offset too */
253882Sache
2542910Sache		diff = offset - tz.tz_minuteswest * 60 - kern_offset;
255882Sache
256882Sache		if (diff != 0) {
2575232Sache#ifdef DEBUG
2585232Sache			fprintf(stderr, "Final diff: %ld secs\n", diff);
2595232Sache#endif
2604090Sache			tv.tv_sec += diff;
2614090Sache			tv.tv_usec = 0;       /* we are restarting here... */
2624090Sache			stv = &tv;
263882Sache		}
2644090Sache	}
265867Sache
2664090Sache	if (tz.tz_dsttime != 0 || tz.tz_minuteswest != 0) {
2674090Sache		tz.tz_dsttime = tz.tz_minuteswest = 0;  /* zone info is garbage */
2684090Sache		stz = &tz;
2694090Sache	}
27015046Sache	if (!wall_clock && stz == NULL)
27115046Sache		stv = NULL;
272867Sache
27315046Sache	/* if init or UTC clock and offset/date will be changed, */
27415046Sache	/* disable RTC modification for a while.                      */
27515046Sache
27615046Sache	if (   (init && stv != NULL)
27715046Sache	    || ((init || !wall_clock) && kern_offset != offset)
27815046Sache	   ) {
2795076Sache		mib[0] = CTL_MACHDEP;
2805076Sache		mib[1] = CPU_DISRTCSET;
2815076Sache		len = sizeof(disrtcset);
2825076Sache		if (sysctl(mib, 2, &disrtcset, &len, NULL, 0) == -1) {
2835076Sache			syslog(LOG_ERR, "sysctl(get_disrtcset): %m");
2845076Sache			return 1;
2855076Sache		}
2865076Sache		if (disrtcset == 0) {
2875076Sache			disrtcset = 1;
28815046Sache			need_restore = True;
2895076Sache			if (sysctl(mib, 2, NULL, NULL, &disrtcset, len) == -1) {
2905076Sache				syslog(LOG_ERR, "sysctl(set_disrtcset): %m");
2914090Sache				return 1;
2924090Sache			}
2933368Sache		}
2944090Sache	}
2954039Sache
29615046Sache	if (   (   (init && (stv != NULL || stz != NULL))
29715046Sache		|| (stz != NULL && stv == NULL)
29815046Sache	       )
2995076Sache	    && settimeofday(stv, stz)
3005076Sache	   ) {
3015076Sache		syslog(LOG_ERR, "settimeofday: %m");
3025076Sache		return 1;
3035076Sache	}
3045076Sache
30515046Sache	/* setting CPU_ADJKERNTZ have a side effect: resettodr(), which */
30615046Sache	/* can be disabled by CPU_DISRTCSET, so if init or UTC clock    */
30715046Sache	/* -- don't write RTC, else write RTC.                          */
30815046Sache
3094090Sache	if (kern_offset != offset) {
3104090Sache		kern_offset = offset;
3114090Sache		mib[0] = CTL_MACHDEP;
3124090Sache		mib[1] = CPU_ADJKERNTZ;
3134090Sache		len = sizeof(kern_offset);
3144090Sache		if (sysctl(mib, 2, NULL, NULL, &kern_offset, len) == -1) {
3154090Sache			syslog(LOG_ERR, "sysctl(update_offset): %m");
3164090Sache			return 1;
3173368Sache		}
3184090Sache	}
319867Sache
32015052Sache	mib[0] = CTL_MACHDEP;
32115052Sache	mib[1] = CPU_WALLCLOCK;
32215052Sache	len = sizeof(wall_clock);
32315052Sache	if (sysctl(mib, 2, NULL, NULL, &wall_clock, len) == -1) {
32415052Sache		syslog(LOG_ERR, "sysctl(put_wallclock): %m");
32515052Sache		return 1;
32615046Sache	}
32715046Sache
3284090Sache	if (need_restore) {
32915046Sache		need_restore = False;
3305078Sache		mib[0] = CTL_MACHDEP;
3315078Sache		mib[1] = CPU_DISRTCSET;
3324090Sache		disrtcset = 0;
3335076Sache		len = sizeof(disrtcset);
3344090Sache		if (sysctl(mib, 2, NULL, NULL, &disrtcset, len) == -1) {
3354090Sache			syslog(LOG_ERR, "sysctl(restore_disrtcset): %m");
3364090Sache			return 1;
3374039Sache		}
3384090Sache	}
3394039Sache
340867Sache/****** End of critical section ******/
341867Sache
34215046Sache	if (init && wall_clock) {
34315057Sache		sleep_mode = False;
3444107Sache		/* wait for signals and acts like -a */
3454107Sache		(void) sigsuspend(&emask);
3464107Sache		goto again;
3474107Sache	}
3484107Sache
3494090Sache	return 0;
350867Sache}
351