adjkerntz.c revision 5232
1111314Snyan/*
2111314Snyan * Copyright (C) 1993, 1994 by Andrew A. Chernov, Moscow, Russia.
3111314Snyan * All rights reserved.
4111314Snyan *
5111314Snyan * Redistribution and use in source and binary forms, with or without
6111314Snyan * modification, are permitted provided that the following conditions
7111314Snyan * are met:
8111314Snyan * 1. Redistributions of source code must retain the above copyright
9111314Snyan *    notice, this list of conditions and the following disclaimer.
10125234Snyan * 2. Redistributions in binary form must reproduce the above copyright
11127520Snyan *    notice, this list of conditions and the following disclaimer in the
12111314Snyan *    documentation and/or other materials provided with the distribution.
13111314Snyan *
14111314Snyan * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND
15111314Snyan * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
16111314Snyan * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
17111314Snyan * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
18122755Snyan * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
19122755Snyan * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
20122755Snyan * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
21122755Snyan * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
22111314Snyan * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
23111314Snyan * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
24111314Snyan * SUCH DAMAGE.
25122056Snyan */
26124795Snyan
27142783Snyan#ifndef lint
28142783Snyanchar copyright[] =
29142783Snyan"@(#)Copyright (C) 1993 by Andrew A. Chernov, Moscow, Russia.\n\
30142783Snyan All rights reserved.\n";
31142783Snyan#endif /* not lint */
32145743Snyan
33111314Snyan/*
34111314Snyan * Andrew A. Chernov   <ache@astral.msk.su>    Dec 20 1993
35111314Snyan *
36111314Snyan * Fix kernel time value if machine run wall CMOS clock
37111314Snyan * (and /etc/wall_cmos_clock file present)
38111314Snyan * using zoneinfo rules or direct TZ environment variable set.
39111314Snyan * Use Joerg Wunsch idea for seconds accurate offset calculation
40111314Snyan * with Garrett Wollman and Bruce Evans fixes.
41111314Snyan *
42111314Snyan */
43111314Snyan#include <stdio.h>
44111314Snyan#include <signal.h>
45111314Snyan#include <stdlib.h>
46111314Snyan#include <unistd.h>
47111314Snyan#include <syslog.h>
48111314Snyan#include <sys/stat.h>
49124795Snyan#include <sys/time.h>
50124795Snyan#include <sys/param.h>
51124795Snyan#include <machine/cpu.h>
52124795Snyan#include <sys/sysctl.h>
53111314Snyan
54111314Snyan#include "pathnames.h"
55111314Snyan
56111314Snyan/*#define DEBUG*/
57111314Snyan#define REPORT_PERIOD (30*60)
58111314Snyan
59111314Snyanvoid fake() {}
60111314Snyan
61111314Snyanint main(argc, argv)
62111314Snyan	int argc;
63124795Snyan	char **argv;
64124795Snyan{
65124795Snyan	struct tm local, utc;
66127520Snyan	struct timeval tv, *stv;
67111314Snyan	struct timezone tz, *stz;
68111314Snyan	int kern_offset;
69111314Snyan	size_t len;
70125234Snyan	int mib[2];
71137526Snyan	/* Avoid time_t here, can be unsigned long or worse */
72137526Snyan	long offset, utcsec, localsec, diff;
73137526Snyan	time_t initial_sec, final_sec;
74137526Snyan	int ch, init = -1;
75137526Snyan	int disrtcset, need_restore = 0;
76124795Snyan	sigset_t mask, emask;
77127520Snyan
78124795Snyan	while ((ch = getopt(argc, argv, "ai")) != EOF)
79111314Snyan		switch((char)ch) {
80111314Snyan		case 'i':               /* initial call, save offset */
81111314Snyan			if (init != -1)
82111314Snyan				goto usage;
83111314Snyan			init = 1;
84111314Snyan			break;
85111314Snyan		case 'a':               /* adjustment call, use saved offset */
86111314Snyan			if (init != -1)
87111314Snyan				goto usage;
88125234Snyan			init = 0;
89111314Snyan			break;
90111314Snyan		default:
91111314Snyan		usage:
92111314Snyan			fprintf(stderr, "Usage:\n\
93111314Snyan\tadjkerntz -i\t(initial call from /etc/rc)\n\
94111314Snyan\tadjkerntz -a\t(adjustment call from crontab)\n");
95111314Snyan  			return 2;
96111314Snyan		}
97111314Snyan	if (init == -1)
98111314Snyan		goto usage;
99111314Snyan
100111314Snyan	if (access(_PATH_CLOCK, F_OK))
101111314Snyan		return 0;
102111314Snyan
103111314Snyan	sigemptyset(&mask);
104111314Snyan	sigemptyset(&emask);
105111314Snyan	sigaddset(&mask, SIGTERM);
106111314Snyan
107111314Snyan	openlog("adjkerntz", LOG_PID|LOG_PERROR, LOG_DAEMON);
108125234Snyan
109111314Snyan	(void) signal(SIGHUP, SIG_IGN);
110111314Snyan
111111314Snyan	if (init && daemon(0, 1)) {
112111314Snyan		syslog(LOG_ERR, "daemon: %m");
113111314Snyan		return 1;
114111314Snyan	}
115111314Snyan
116111314Snyanagain:
117111314Snyan
118111314Snyan	(void) sigprocmask(SIG_BLOCK, &mask, NULL);
119111314Snyan	(void) signal(SIGTERM, fake);
120111314Snyan
121111314Snyan/****** Critical section, do all things as fast as possible ******/
122111314Snyan
123111314Snyan	/* get local CMOS clock and possible kernel offset */
124111314Snyan	if (gettimeofday(&tv, &tz)) {
125111314Snyan		syslog(LOG_ERR, "gettimeofday: %m");
126111314Snyan		return 1;
127111314Snyan	}
128111314Snyan
129111314Snyan	/* get the actual local timezone difference */
130111314Snyan	initial_sec = tv.tv_sec;
131111314Snyan	local = *localtime(&initial_sec);
132111314Snyan	utc = *gmtime(&initial_sec);
133111314Snyan
134111314Snyan	/* calculate local CMOS diff from GMT */
135111314Snyan
136111314Snyan	utcsec = timelocal(&utc);
137124795Snyan	localsec = timelocal(&local);
138111314Snyan	if (utcsec == -1 || localsec == -1) {
139111314Snyan		/*
140111314Snyan		 * XXX user can only control local time, and it is
141111314Snyan		 * unacceptable to fail here for init.  2:30 am in the
142124795Snyan		 * middle of the nonexistent hour means 3:30 am.
143124795Snyan		 */
144111314Snyan		syslog(LOG_WARNING,
145111314Snyan		"Nonexistent local time -- will retry after %d secs", REPORT_PERIOD);
146111314Snyan		(void) signal(SIGTERM, SIG_DFL);
147111314Snyan		(void) sigprocmask(SIG_UNBLOCK, &mask, NULL);
148111314Snyan		(void) sleep(REPORT_PERIOD);
149111314Snyan		goto again;
150111314Snyan	}
151111314Snyan	offset = utcsec - localsec;
152111314Snyan#ifdef DEBUG
153111314Snyan	fprintf(stderr, "Initial offset: %ld secs\n", offset);
154111314Snyan#endif
155111314Snyan
156111314Snyan	mib[0] = CTL_MACHDEP;
157111314Snyan	mib[1] = CPU_ADJKERNTZ;
158111314Snyan	len = sizeof(kern_offset);
159124795Snyan	if (sysctl(mib, 2, &kern_offset, &len, NULL, 0) == -1) {
160111314Snyan		syslog(LOG_ERR, "sysctl(get_offset): %m");
161111314Snyan		return 1;
162111314Snyan	}
163111314Snyan
164111314Snyan	stv = NULL;
165111314Snyan	stz = NULL;
166111314Snyan
167111314Snyan	/* correct the kerneltime for this diffs */
168111314Snyan	/* subtract kernel offset, if present, old offset too */
169111314Snyan
170111314Snyan	diff = offset - tz.tz_minuteswest * 60 - kern_offset;
171111314Snyan
172111314Snyan	if (diff != 0) {
173111314Snyan#ifdef DEBUG
174111314Snyan		fprintf(stderr, "Initial diff: %ld secs\n", diff);
175111314Snyan#endif
176124795Snyan		/* Yet one step for final time */
177111314Snyan
178111314Snyan		final_sec = tv.tv_sec + diff;
179111314Snyan
180151051Sglebius		/* get the actual local timezone difference */
181151051Sglebius		local = *localtime(&final_sec);
182151051Sglebius		utc = *gmtime(&final_sec);
183151051Sglebius
184111314Snyan		utcsec = timelocal(&utc);
185124408Snyan		localsec = timelocal(&local);
186124408Snyan		if (utcsec == -1 || localsec == -1) {
187111314Snyan			/*
188111314Snyan			 * XXX as above.  The user has even less control,
189111314Snyan			 * but perhaps we never get here.
190177586Sjkim			 */
191177586Sjkim			syslog(LOG_WARNING,
192191954Skuriyama		"Nonexistent (final) local time -- will retry after %d secs", REPORT_PERIOD);
193177586Sjkim			(void) signal(SIGTERM, SIG_DFL);
194111314Snyan			(void) sigprocmask(SIG_UNBLOCK, &mask, NULL);
195111314Snyan			(void) sleep(REPORT_PERIOD);
196111314Snyan			goto again;
197111314Snyan		}
198160813Smarcel		offset = utcsec - localsec;
199160813Smarcel#ifdef DEBUG
200163494Simp		fprintf(stderr, "Final offset: %ld secs\n", offset);
201160813Smarcel#endif
202181905Sed
203160813Smarcel		/* correct the kerneltime for this diffs */
204160813Smarcel		/* subtract kernel offset, if present, old offset too */
205160813Smarcel
206160813Smarcel		diff = offset - tz.tz_minuteswest * 60 - kern_offset;
207160813Smarcel
208160813Smarcel		if (diff != 0) {
209160813Smarcel#ifdef DEBUG
210160813Smarcel			fprintf(stderr, "Final diff: %ld secs\n", diff);
211160813Smarcel#endif
212160813Smarcel			tv.tv_sec += diff;
213160813Smarcel			tv.tv_usec = 0;       /* we are restarting here... */
214160813Smarcel			stv = &tv;
215160813Smarcel		}
216160813Smarcel	}
217160813Smarcel
218160813Smarcel	if (tz.tz_dsttime != 0 || tz.tz_minuteswest != 0) {
219160813Smarcel		tz.tz_dsttime = tz.tz_minuteswest = 0;  /* zone info is garbage */
220160813Smarcel		stz = &tz;
221160813Smarcel	}
222160813Smarcel
223160813Smarcel	/* if init and something will be changed, don't touch RTC at all */
224160813Smarcel	if (init && (stv != NULL || kern_offset != offset)) {
225160813Smarcel		mib[0] = CTL_MACHDEP;
226160813Smarcel		mib[1] = CPU_DISRTCSET;
227160813Smarcel		len = sizeof(disrtcset);
228160813Smarcel		if (sysctl(mib, 2, &disrtcset, &len, NULL, 0) == -1) {
229160813Smarcel			syslog(LOG_ERR, "sysctl(get_disrtcset): %m");
230160813Smarcel			return 1;
231160813Smarcel		}
232160813Smarcel		if (disrtcset == 0) {
233160813Smarcel			disrtcset = 1;
234160813Smarcel			need_restore = 1;
235160813Smarcel			if (sysctl(mib, 2, NULL, NULL, &disrtcset, len) == -1) {
236111314Snyan				syslog(LOG_ERR, "sysctl(set_disrtcset): %m");
237111314Snyan				return 1;
238111314Snyan			}
239124795Snyan		}
240111314Snyan	}
241111314Snyan
242111314Snyan	if ((   (init && (stv != NULL || stz != NULL))
243111314Snyan	     || (stz != NULL && stv == NULL)
244111314Snyan	    )
245111314Snyan	    && settimeofday(stv, stz)
246111314Snyan	   ) {
247111314Snyan		syslog(LOG_ERR, "settimeofday: %m");
248111314Snyan		return 1;
249111314Snyan	}
250111314Snyan
251111314Snyan	/* init: don't write RTC, !init: write RTC */
252111314Snyan	if (kern_offset != offset) {
253111314Snyan		kern_offset = offset;
254111314Snyan		mib[0] = CTL_MACHDEP;
255111314Snyan		mib[1] = CPU_ADJKERNTZ;
256111314Snyan		len = sizeof(kern_offset);
257111314Snyan		if (sysctl(mib, 2, NULL, NULL, &kern_offset, len) == -1) {
258111314Snyan			syslog(LOG_ERR, "sysctl(update_offset): %m");
259111314Snyan			return 1;
260111314Snyan		}
261111314Snyan	}
262111314Snyan
263111314Snyan	if (need_restore) {
264111314Snyan		need_restore = 0;
265111314Snyan		mib[0] = CTL_MACHDEP;
266111314Snyan		mib[1] = CPU_DISRTCSET;
267111314Snyan		disrtcset = 0;
268111314Snyan		len = sizeof(disrtcset);
269111314Snyan		if (sysctl(mib, 2, NULL, NULL, &disrtcset, len) == -1) {
270188257Swkoszek			syslog(LOG_ERR, "sysctl(restore_disrtcset): %m");
271188307Swkoszek			return 1;
272188307Swkoszek		}
273188307Swkoszek	}
274188307Swkoszek
275188307Swkoszek/****** End of critical section ******/
276111314Snyan
277111314Snyan	if (init) {
278111314Snyan		init = 0;
279111314Snyan		/* wait for signals and acts like -a */
280111314Snyan		(void) sigsuspend(&emask);
281111314Snyan		goto again;
282111314Snyan	}
283111314Snyan
284111314Snyan	return 0;
285111314Snyan}
286111314Snyan