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