adjkerntz.c revision 92536
1/*
2 * Copyright (C) 1993-1998 by Andrey A. Chernov, Moscow, Russia.
3 * All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
7 * are met:
8 * 1. Redistributions of source code must retain the above copyright
9 *    notice, this list of conditions and the following disclaimer.
10 * 2. Redistributions in binary form must reproduce the above copyright
11 *    notice, this list of conditions and the following disclaimer in the
12 *    documentation and/or other materials provided with the distribution.
13 *
14 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND
15 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
16 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
17 * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
18 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
19 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
20 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
21 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
22 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
23 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
24 * SUCH DAMAGE.
25 */
26
27#ifndef lint
28static const char copyright[] =
29"@(#)Copyright (C) 1993-1996 by Andrey A. Chernov, Moscow, Russia.\n\
30 All rights reserved.\n";
31#endif /* not lint */
32
33#ifndef lint
34static const char rcsid[] =
35  "$FreeBSD: head/sbin/adjkerntz/adjkerntz.c 92536 2002-03-18 04:55:09Z imp $";
36#endif /* not lint */
37
38/*
39 * Andrey A. Chernov   <ache@astral.msk.su>    Dec 20 1993
40 *
41 * Fix kernel time value if machine run wall CMOS clock
42 * (and /etc/wall_cmos_clock file present)
43 * using zoneinfo rules or direct TZ environment variable set.
44 * Use Joerg Wunsch idea for seconds accurate offset calculation
45 * with Garrett Wollman and Bruce Evans fixes.
46 *
47 */
48#include <stdio.h>
49#include <signal.h>
50#include <stdlib.h>
51#include <unistd.h>
52#include <syslog.h>
53#include <sys/time.h>
54#include <sys/param.h>
55#include <machine/cpu.h>
56#include <sys/sysctl.h>
57
58#include "pathnames.h"
59
60/*#define DEBUG*/
61
62#define True (1)
63#define False (0)
64#define Unknown (-1)
65
66#define REPORT_PERIOD (30*60)
67
68static void fake(int);
69static void usage(void);
70
71static void
72fake(int unused __unused)
73{
74
75	/* Do nothing. */
76}
77
78int
79main(int argc, char *argv[])
80{
81	struct tm local;
82	struct timeval tv, *stv;
83	struct timezone tz, *stz;
84	int kern_offset, wall_clock, disrtcset;
85	size_t len;
86	int mib[2];
87	/* Avoid time_t here, can be unsigned long or worse */
88	long offset, localsec, diff;
89	time_t initial_sec, final_sec;
90	int ch;
91	int initial_isdst = -1, final_isdst;
92	int need_restore = False, sleep_mode = False, looping,
93	    init = Unknown;
94	sigset_t mask, emask;
95
96	while ((ch = getopt(argc, argv, "ais")) != -1)
97		switch((char)ch) {
98		case 'i':               /* initial call, save offset */
99			if (init != Unknown)
100				usage();
101			init = True;
102			break;
103		case 'a':               /* adjustment call, use saved offset */
104			if (init != Unknown)
105				usage();
106			init = False;
107			break;
108		case 's':
109			sleep_mode = True;
110			break;
111		default:
112			usage();
113		}
114	if (init == Unknown)
115		usage();
116
117	if (access(_PATH_CLOCK, F_OK) != 0)
118		return 0;
119
120	if (init)
121		sleep_mode = True;
122
123	sigemptyset(&mask);
124	sigemptyset(&emask);
125	sigaddset(&mask, SIGTERM);
126
127	openlog("adjkerntz", LOG_PID|LOG_PERROR, LOG_DAEMON);
128
129	(void) signal(SIGHUP, SIG_IGN);
130
131	if (init && daemon(0, 1)) {
132		syslog(LOG_ERR, "daemon: %m");
133		return 1;
134	}
135
136again:
137	(void) sigprocmask(SIG_BLOCK, &mask, NULL);
138	(void) signal(SIGTERM, fake);
139
140	diff = 0;
141	stv = NULL;
142	stz = NULL;
143	looping = False;
144
145	wall_clock = (access(_PATH_CLOCK, F_OK) == 0);
146	if (init && !sleep_mode) {
147		init = False;
148		if (!wall_clock)
149			return 0;
150	}
151
152	mib[0] = CTL_MACHDEP;
153	mib[1] = CPU_ADJKERNTZ;
154	len = sizeof(kern_offset);
155	if (sysctl(mib, 2, &kern_offset, &len, NULL, 0) == -1) {
156		syslog(LOG_ERR, "sysctl(get_offset): %m");
157		return 1;
158	}
159
160/****** Critical section, do all things as fast as possible ******/
161
162	/* get local CMOS clock and possible kernel offset */
163	if (gettimeofday(&tv, &tz)) {
164		syslog(LOG_ERR, "gettimeofday: %m");
165		return 1;
166	}
167
168	/* get the actual local timezone difference */
169	initial_sec = tv.tv_sec;
170
171recalculate:
172	local = *localtime(&initial_sec);
173	if (diff == 0)
174		initial_isdst = local.tm_isdst;
175	local.tm_isdst = initial_isdst;
176
177	/* calculate local CMOS diff from GMT */
178
179	localsec = mktime(&local);
180	if (localsec == -1) {
181		/*
182		 * XXX user can only control local time, and it is
183		 * unacceptable to fail here for init.  2:30 am in the
184		 * middle of the nonexistent hour means 3:30 am.
185		 */
186		if (!sleep_mode) {
187			syslog(LOG_WARNING,
188			"Warning: nonexistent local time, try to run later.");
189			syslog(LOG_WARNING, "Giving up.");
190			return 1;
191		}
192		syslog(LOG_WARNING,
193			"Warning: nonexistent local time.");
194		syslog(LOG_WARNING, "Will retry after %d minutes.",
195			REPORT_PERIOD / 60);
196		(void) signal(SIGTERM, SIG_DFL);
197		(void) sigprocmask(SIG_UNBLOCK, &mask, NULL);
198		(void) sleep(REPORT_PERIOD);
199		goto again;
200	}
201	offset = -local.tm_gmtoff;
202#ifdef DEBUG
203	fprintf(stderr, "Initial offset: %ld secs\n", offset);
204#endif
205
206	/* correct the kerneltime for this diffs */
207	/* subtract kernel offset, if present, old offset too */
208
209	diff = offset - tz.tz_minuteswest * 60 - kern_offset;
210
211	if (diff != 0) {
212#ifdef DEBUG
213		fprintf(stderr, "Initial diff: %ld secs\n", diff);
214#endif
215		/* Yet one step for final time */
216
217		final_sec = initial_sec + diff;
218
219		/* get the actual local timezone difference */
220		local = *localtime(&final_sec);
221		final_isdst = diff < 0 ? initial_isdst : local.tm_isdst;
222		if (diff > 0 && initial_isdst != final_isdst) {
223			if (looping)
224				goto bad_final;
225			looping = True;
226			initial_isdst = final_isdst;
227			goto recalculate;
228		}
229		local.tm_isdst =  final_isdst;
230
231		localsec = mktime(&local);
232		if (localsec == -1) {
233		bad_final:
234			/*
235			 * XXX as above.  The user has even less control,
236			 * but perhaps we never get here.
237			 */
238			if (!sleep_mode) {
239				syslog(LOG_WARNING,
240					"Warning: nonexistent final local time, try to run later.");
241				syslog(LOG_WARNING, "Giving up.");
242				return 1;
243			}
244			syslog(LOG_WARNING,
245				"Warning: nonexistent final local time.");
246			syslog(LOG_WARNING, "Will retry after %d minutes.",
247				REPORT_PERIOD / 60);
248			(void) signal(SIGTERM, SIG_DFL);
249			(void) sigprocmask(SIG_UNBLOCK, &mask, NULL);
250			(void) sleep(REPORT_PERIOD);
251			goto again;
252		}
253		offset = -local.tm_gmtoff;
254#ifdef DEBUG
255		fprintf(stderr, "Final offset: %ld secs\n", offset);
256#endif
257
258		/* correct the kerneltime for this diffs */
259		/* subtract kernel offset, if present, old offset too */
260
261		diff = offset - tz.tz_minuteswest * 60 - kern_offset;
262
263		if (diff != 0) {
264#ifdef DEBUG
265			fprintf(stderr, "Final diff: %ld secs\n", diff);
266#endif
267			/*
268			 * stv is abused as a flag.  The important value
269			 * is in `diff'.
270			 */
271			stv = &tv;
272		}
273	}
274
275	if (tz.tz_dsttime != 0 || tz.tz_minuteswest != 0) {
276		tz.tz_dsttime = tz.tz_minuteswest = 0;  /* zone info is garbage */
277		stz = &tz;
278	}
279	if (!wall_clock && stz == NULL)
280		stv = NULL;
281
282	/* if init or UTC clock and offset/date will be changed, */
283	/* disable RTC modification for a while.                      */
284
285	if (   (init && stv != NULL)
286	    || ((init || !wall_clock) && kern_offset != offset)
287	   ) {
288		mib[0] = CTL_MACHDEP;
289		mib[1] = CPU_DISRTCSET;
290		len = sizeof(disrtcset);
291		if (sysctl(mib, 2, &disrtcset, &len, NULL, 0) == -1) {
292			syslog(LOG_ERR, "sysctl(get_disrtcset): %m");
293			return 1;
294		}
295		if (disrtcset == 0) {
296			disrtcset = 1;
297			need_restore = True;
298			if (sysctl(mib, 2, NULL, NULL, &disrtcset, len) == -1) {
299				syslog(LOG_ERR, "sysctl(set_disrtcset): %m");
300				return 1;
301			}
302		}
303	}
304
305	if (   (init && (stv != NULL || stz != NULL))
306	    || (stz != NULL && stv == NULL)
307	   ) {
308		if (stv != NULL) {
309			/*
310			 * Get the time again, as close as possible to
311			 * adjusting it, to minimise drift.
312			 * XXX we'd better not fail between here and
313			 * restoring disrtcset, since we don't clean up
314			 * anything.
315			 */
316			if (gettimeofday(&tv, (struct timezone *)NULL)) {
317				syslog(LOG_ERR, "gettimeofday: %m");
318				return 1;
319			}
320			tv.tv_sec += diff;
321			stv = &tv;
322		}
323		if (settimeofday(stv, stz)) {
324			syslog(LOG_ERR, "settimeofday: %m");
325			return 1;
326		}
327	}
328
329	/* setting CPU_ADJKERNTZ have a side effect: resettodr(), which */
330	/* can be disabled by CPU_DISRTCSET, so if init or UTC clock    */
331	/* -- don't write RTC, else write RTC.                          */
332
333	if (kern_offset != offset) {
334		kern_offset = offset;
335		mib[0] = CTL_MACHDEP;
336		mib[1] = CPU_ADJKERNTZ;
337		len = sizeof(kern_offset);
338		if (sysctl(mib, 2, NULL, NULL, &kern_offset, len) == -1) {
339			syslog(LOG_ERR, "sysctl(update_offset): %m");
340			return 1;
341		}
342	}
343
344	mib[0] = CTL_MACHDEP;
345	mib[1] = CPU_WALLCLOCK;
346	len = sizeof(wall_clock);
347	if (sysctl(mib, 2, NULL, NULL, &wall_clock, len) == -1) {
348		syslog(LOG_ERR, "sysctl(put_wallclock): %m");
349		return 1;
350	}
351
352	if (need_restore) {
353		need_restore = False;
354		mib[0] = CTL_MACHDEP;
355		mib[1] = CPU_DISRTCSET;
356		disrtcset = 0;
357		len = sizeof(disrtcset);
358		if (sysctl(mib, 2, NULL, NULL, &disrtcset, len) == -1) {
359			syslog(LOG_ERR, "sysctl(restore_disrtcset): %m");
360			return 1;
361		}
362	}
363
364/****** End of critical section ******/
365
366	if (init && wall_clock) {
367		sleep_mode = False;
368		/* wait for signals and acts like -a */
369		(void) sigsuspend(&emask);
370		goto again;
371	}
372
373	return 0;
374}
375
376static void
377usage(void)
378{
379	fprintf(stderr, "%s\n%s\n%s\n%s\n",
380		"usage: adjkerntz -i",
381		"\t\t(initial call from /etc/rc)",
382		"       adjkerntz -a [-s]",
383		"\t\t(adjustment call, -s for sleep/retry mode)");
384	exit(2);
385}
386