adjkerntz.c revision 8871
1193323Sed/*
2193323Sed * Copyright (C) 1993, 1994, 1995 by Andrey A. Chernov, Moscow, Russia.
3193323Sed * All rights reserved.
4193323Sed *
5193323Sed * Redistribution and use in source and binary forms, with or without
6193323Sed * modification, are permitted provided that the following conditions
7193323Sed * are met:
8193323Sed * 1. Redistributions of source code must retain the above copyright
9193323Sed *    notice, this list of conditions and the following disclaimer.
10193323Sed * 2. Redistributions in binary form must reproduce the above copyright
11193323Sed *    notice, this list of conditions and the following disclaimer in the
12193323Sed *    documentation and/or other materials provided with the distribution.
13193323Sed *
14193323Sed * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND
15193323Sed * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
16193323Sed * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
17261991Sdim * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
18193323Sed * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
19276479Sdim * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
20276479Sdim * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
21193323Sed * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
22193323Sed * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
23193323Sed * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
24276479Sdim * SUCH DAMAGE.
25210299Sed */
26193323Sed
27210299Sed#ifndef lint
28193323Sedchar copyright[] =
29193323Sed"@(#)Copyright (C) 1993, 1994, 1995 by Andrey A. Chernov, Moscow, Russia.\n\
30193323Sed All rights reserved.\n";
31276479Sdim#endif /* not lint */
32249423Sdim
33249423Sdim/*
34276479Sdim * Andrey A. Chernov   <ache@astral.msk.su>    Dec 20 1993
35249423Sdim *
36249423Sdim * Fix kernel time value if machine run wall CMOS clock
37249423Sdim * (and /etc/wall_cmos_clock file present)
38249423Sdim * using zoneinfo rules or direct TZ environment variable set.
39249423Sdim * Use Joerg Wunsch idea for seconds accurate offset calculation
40288943Sdim * with Garrett Wollman and Bruce Evans fixes.
41288943Sdim *
42198090Srdivacky */
43193323Sed#include <stdio.h>
44193323Sed#include <signal.h>
45193323Sed#include <stdlib.h>
46193323Sed#include <unistd.h>
47218893Sdim#include <syslog.h>
48276479Sdim#include <sys/stat.h>
49193323Sed#include <sys/time.h>
50193323Sed#include <sys/param.h>
51193323Sed#include <machine/cpu.h>
52193323Sed#include <sys/sysctl.h>
53218893Sdim
54193323Sed#include "pathnames.h"
55193323Sed
56193323Sed/*#define DEBUG*/
57193323Sed#define REPORT_PERIOD (30*60)
58193323Sed
59193323Sedvoid fake() {}
60193323Sed
61239462Sdimint main(argc, argv)
62239462Sdim	int argc;
63288943Sdim	char **argv;
64288943Sdim{
65234353Sdim	struct tm local, utc;
66234353Sdim	struct timeval tv, *stv;
67193323Sed	struct timezone tz, *stz;
68193323Sed	int kern_offset;
69280031Sdim	size_t len;
70193323Sed	int mib[2];
71251662Sdim	/* Avoid time_t here, can be unsigned long or worse */
72218893Sdim	long offset, utcsec, localsec, diff;
73218893Sdim	time_t initial_sec, final_sec;
74218893Sdim	int ch, init = -1;
75276479Sdim	int initial_isdst = -1, final_isdst, looping;
76276479Sdim	int disrtcset, need_restore = 0;
77218893Sdim	sigset_t mask, emask;
78280031Sdim
79280031Sdim	while ((ch = getopt(argc, argv, "ai")) != EOF)
80280031Sdim		switch((char)ch) {
81249423Sdim		case 'i':               /* initial call, save offset */
82193323Sed			if (init != -1)
83193323Sed				goto usage;
84193323Sed			init = 1;
85288943Sdim			break;
86218893Sdim		case 'a':               /* adjustment call, use saved offset */
87288943Sdim			if (init != -1)
88288943Sdim				goto usage;
89193323Sed			init = 0;
90288943Sdim			break;
91276479Sdim		default:
92276479Sdim		usage:
93288943Sdim			fprintf(stderr, "Usage:\n\
94276479Sdim\tadjkerntz -i\t(initial call from /etc/rc)\n\
95193323Sed\tadjkerntz -a\t(adjustment call from crontab)\n");
96198090Srdivacky  			return 2;
97193323Sed		}
98218893Sdim	if (init == -1)
99207618Srdivacky		goto usage;
100207618Srdivacky
101193323Sed	if (access(_PATH_CLOCK, F_OK))
102207618Srdivacky		return 0;
103218893Sdim
104207618Srdivacky	sigemptyset(&mask);
105193323Sed	sigemptyset(&emask);
106210299Sed	sigaddset(&mask, SIGTERM);
107210299Sed
108207618Srdivacky	openlog("adjkerntz", LOG_PID|LOG_PERROR, LOG_DAEMON);
109193323Sed
110207618Srdivacky	(void) signal(SIGHUP, SIG_IGN);
111193323Sed
112207618Srdivacky	if (init && daemon(0, 1)) {
113193323Sed		syslog(LOG_ERR, "daemon: %m");
114207618Srdivacky		return 1;
115193323Sed	}
116251662Sdim
117251662Sdimagain:
118276479Sdim
119276479Sdim	(void) sigprocmask(SIG_BLOCK, &mask, NULL);
120276479Sdim	(void) signal(SIGTERM, fake);
121276479Sdim
122276479Sdim	diff = 0;
123276479Sdim	stv = NULL;
124207618Srdivacky	stz = NULL;
125193323Sed	looping = 0;
126207618Srdivacky
127218893Sdim	mib[0] = CTL_MACHDEP;
128288943Sdim	mib[1] = CPU_ADJKERNTZ;
129288943Sdim	len = sizeof(kern_offset);
130288943Sdim	if (sysctl(mib, 2, &kern_offset, &len, NULL, 0) == -1) {
131207618Srdivacky		syslog(LOG_ERR, "sysctl(get_offset): %m");
132207618Srdivacky		return 1;
133288943Sdim	}
134193323Sed
135193323Sed/****** Critical section, do all things as fast as possible ******/
136288943Sdim
137193323Sed	/* get local CMOS clock and possible kernel offset */
138193323Sed	if (gettimeofday(&tv, &tz)) {
139193323Sed		syslog(LOG_ERR, "gettimeofday: %m");
140193323Sed		return 1;
141193323Sed	}
142261991Sdim
143261991Sdim	/* get the actual local timezone difference */
144280031Sdim	initial_sec = tv.tv_sec;
145280031Sdim
146280031Sdimrecalculate:
147280031Sdim	local = *localtime(&initial_sec);
148193323Sed	if (diff == 0)
149280031Sdim		initial_isdst = local.tm_isdst;
150193323Sed	utc = *gmtime(&initial_sec);
151280031Sdim	local.tm_isdst = utc.tm_isdst = initial_isdst;
152221345Sdim
153193323Sed	/* calculate local CMOS diff from GMT */
154193323Sed
155198090Srdivacky	utcsec = mktime(&utc);
156193323Sed	localsec = mktime(&local);
157193323Sed	if (utcsec == -1 || localsec == -1) {
158193323Sed		/*
159193323Sed		 * XXX user can only control local time, and it is
160226633Sdim		 * unacceptable to fail here for init.  2:30 am in the
161221345Sdim		 * middle of the nonexistent hour means 3:30 am.
162223017Sdim		 */
163223017Sdim		syslog(LOG_WARNING,
164221345Sdim		"Nonexistent local time -- will retry after %d secs", REPORT_PERIOD);
165221345Sdim		(void) signal(SIGTERM, SIG_DFL);
166276479Sdim		(void) sigprocmask(SIG_UNBLOCK, &mask, NULL);
167276479Sdim		(void) sleep(REPORT_PERIOD);
168276479Sdim		goto again;
169288943Sdim	}
170288943Sdim	offset = utcsec - localsec;
171288943Sdim#ifdef DEBUG
172193323Sed	fprintf(stderr, "Initial offset: %ld secs\n", offset);
173218893Sdim#endif
174193323Sed
175193323Sed	/* correct the kerneltime for this diffs */
176276479Sdim	/* subtract kernel offset, if present, old offset too */
177276479Sdim
178276479Sdim	diff = offset - tz.tz_minuteswest * 60 - kern_offset;
179276479Sdim
180276479Sdim	if (diff != 0) {
181276479Sdim#ifdef DEBUG
182276479Sdim		fprintf(stderr, "Initial diff: %ld secs\n", diff);
183276479Sdim#endif
184276479Sdim		/* Yet one step for final time */
185276479Sdim
186276479Sdim		final_sec = initial_sec + diff;
187276479Sdim
188276479Sdim		/* get the actual local timezone difference */
189276479Sdim		local = *localtime(&final_sec);
190276479Sdim		final_isdst = diff < 0 ? initial_isdst : local.tm_isdst;
191276479Sdim		if (diff > 0 && initial_isdst != final_isdst) {
192276479Sdim			if (looping)
193276479Sdim				goto bad_final;
194276479Sdim			looping++;
195276479Sdim			initial_isdst = final_isdst;
196276479Sdim			goto recalculate;
197276479Sdim		}
198276479Sdim		utc = *gmtime(&final_sec);
199276479Sdim		local.tm_isdst = utc.tm_isdst = final_isdst;
200276479Sdim
201276479Sdim		utcsec = mktime(&utc);
202276479Sdim		localsec = mktime(&local);
203276479Sdim		if (utcsec == -1 || localsec == -1) {
204276479Sdim		bad_final:
205276479Sdim			/*
206276479Sdim			 * XXX as above.  The user has even less control,
207276479Sdim			 * but perhaps we never get here.
208276479Sdim			 */
209276479Sdim			syslog(LOG_WARNING,
210276479Sdim		"Nonexistent (final) local time -- will retry after %d secs", REPORT_PERIOD);
211276479Sdim			(void) signal(SIGTERM, SIG_DFL);
212276479Sdim			(void) sigprocmask(SIG_UNBLOCK, &mask, NULL);
213276479Sdim			(void) sleep(REPORT_PERIOD);
214276479Sdim			goto again;
215276479Sdim		}
216276479Sdim		offset = utcsec - localsec;
217276479Sdim#ifdef DEBUG
218276479Sdim		fprintf(stderr, "Final offset: %ld secs\n", offset);
219276479Sdim#endif
220276479Sdim
221276479Sdim		/* correct the kerneltime for this diffs */
222276479Sdim		/* subtract kernel offset, if present, old offset too */
223276479Sdim
224276479Sdim		diff = offset - tz.tz_minuteswest * 60 - kern_offset;
225276479Sdim
226276479Sdim		if (diff != 0) {
227276479Sdim#ifdef DEBUG
228276479Sdim			fprintf(stderr, "Final diff: %ld secs\n", diff);
229276479Sdim#endif
230276479Sdim			tv.tv_sec += diff;
231276479Sdim			tv.tv_usec = 0;       /* we are restarting here... */
232276479Sdim			stv = &tv;
233276479Sdim		}
234276479Sdim	}
235276479Sdim
236276479Sdim	if (tz.tz_dsttime != 0 || tz.tz_minuteswest != 0) {
237276479Sdim		tz.tz_dsttime = tz.tz_minuteswest = 0;  /* zone info is garbage */
238276479Sdim		stz = &tz;
239276479Sdim	}
240276479Sdim
241276479Sdim	/* if init and something will be changed, don't touch RTC at all */
242276479Sdim	if (init && (stv != NULL || kern_offset != offset)) {
243276479Sdim		mib[0] = CTL_MACHDEP;
244276479Sdim		mib[1] = CPU_DISRTCSET;
245276479Sdim		len = sizeof(disrtcset);
246276479Sdim		if (sysctl(mib, 2, &disrtcset, &len, NULL, 0) == -1) {
247276479Sdim			syslog(LOG_ERR, "sysctl(get_disrtcset): %m");
248276479Sdim			return 1;
249288943Sdim		}
250288943Sdim		if (disrtcset == 0) {
251288943Sdim			disrtcset = 1;
252288943Sdim			need_restore = 1;
253288943Sdim			if (sysctl(mib, 2, NULL, NULL, &disrtcset, len) == -1) {
254288943Sdim				syslog(LOG_ERR, "sysctl(set_disrtcset): %m");
255288943Sdim				return 1;
256288943Sdim			}
257288943Sdim		}
258288943Sdim	}
259288943Sdim
260288943Sdim	if ((   (init && (stv != NULL || stz != NULL))
261288943Sdim	     || (stz != NULL && stv == NULL)
262288943Sdim	    )
263276479Sdim	    && settimeofday(stv, stz)
264276479Sdim	   ) {
265276479Sdim		syslog(LOG_ERR, "settimeofday: %m");
266276479Sdim		return 1;
267276479Sdim	}
268276479Sdim
269276479Sdim	/* init: don't write RTC, !init: write RTC */
270276479Sdim	if (kern_offset != offset) {
271276479Sdim		kern_offset = offset;
272276479Sdim		mib[0] = CTL_MACHDEP;
273276479Sdim		mib[1] = CPU_ADJKERNTZ;
274276479Sdim		len = sizeof(kern_offset);
275276479Sdim		if (sysctl(mib, 2, NULL, NULL, &kern_offset, len) == -1) {
276276479Sdim			syslog(LOG_ERR, "sysctl(update_offset): %m");
277276479Sdim			return 1;
278276479Sdim		}
279276479Sdim	}
280276479Sdim
281276479Sdim	if (need_restore) {
282276479Sdim		need_restore = 0;
283276479Sdim		mib[0] = CTL_MACHDEP;
284276479Sdim		mib[1] = CPU_DISRTCSET;
285276479Sdim		disrtcset = 0;
286276479Sdim		len = sizeof(disrtcset);
287276479Sdim		if (sysctl(mib, 2, NULL, NULL, &disrtcset, len) == -1) {
288276479Sdim			syslog(LOG_ERR, "sysctl(restore_disrtcset): %m");
289276479Sdim			return 1;
290276479Sdim		}
291276479Sdim	}
292276479Sdim
293276479Sdim/****** End of critical section ******/
294276479Sdim
295276479Sdim	if (init) {
296276479Sdim		init = 0;
297276479Sdim		/* wait for signals and acts like -a */
298276479Sdim		(void) sigsuspend(&emask);
299276479Sdim		goto again;
300276479Sdim	}
301296417Sdim
302296417Sdim	return 0;
303276479Sdim}
304276479Sdim