xref: /freebsd/sbin/adjkerntz/adjkerntz.c (revision cf83038cec6874aafacf56a1cac531d079af016b)
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
31 static 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$");
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 <errno.h>
50 #include <stdio.h>
51 #include <signal.h>
52 #include <stdlib.h>
53 #include <unistd.h>
54 #include <syslog.h>
55 #include <sys/time.h>
56 #include <sys/param.h>
57 #include <machine/cpu.h>
58 #include <sys/sysctl.h>
59 
60 #include "pathnames.h"
61 
62 /*#define DEBUG*/
63 
64 #define True (1)
65 #define False (0)
66 #define Unknown (-1)
67 
68 #define REPORT_PERIOD (30*60)
69 
70 static void fake(int);
71 static void usage(void);
72 
73 static void
74 fake(int unused __unused)
75 {
76 
77 	/* Do nothing. */
78 }
79 
80 int
81 main(int argc, char *argv[])
82 {
83 	struct tm local;
84 	struct timeval tv, *stv;
85 	struct timezone tz, *stz;
86 	int kern_offset, wall_clock, disrtcset;
87 	size_t len;
88 	/* Avoid time_t here, can be unsigned long or worse */
89 	long offset, localsec, diff;
90 	time_t initial_sec, final_sec;
91 	int ch;
92 	int initial_isdst = -1, final_isdst;
93 	int need_restore = False, sleep_mode = False, looping,
94 	    init = Unknown;
95 	sigset_t mask, emask;
96 
97 	while ((ch = getopt(argc, argv, "ais")) != -1)
98 		switch((char)ch) {
99 		case 'i':               /* initial call, save offset */
100 			if (init != Unknown)
101 				usage();
102 			init = True;
103 			break;
104 		case 'a':               /* adjustment call, use saved offset */
105 			if (init != Unknown)
106 				usage();
107 			init = False;
108 			break;
109 		case 's':
110 			sleep_mode = True;
111 			break;
112 		default:
113 			usage();
114 		}
115 	if (init == Unknown)
116 		usage();
117 
118 	if (access(_PATH_CLOCK, F_OK) != 0)
119 		return 0;
120 
121 	if (init)
122 		sleep_mode = True;
123 
124 	sigemptyset(&mask);
125 	sigemptyset(&emask);
126 	sigaddset(&mask, SIGTERM);
127 
128 	openlog("adjkerntz", LOG_PID|LOG_PERROR, LOG_DAEMON);
129 
130 	(void) signal(SIGHUP, SIG_IGN);
131 
132 	if (init && daemon(0,
133 #ifdef DEBUG
134 	    1
135 #else
136 	    0
137 #endif
138 	    )) {
139 		syslog(LOG_ERR, "daemon: %m");
140 		return 1;
141 	}
142 
143 again:
144 	(void) sigprocmask(SIG_BLOCK, &mask, NULL);
145 	(void) signal(SIGTERM, fake);
146 
147 	diff = 0;
148 	stv = NULL;
149 	stz = NULL;
150 	looping = False;
151 
152 	wall_clock = (access(_PATH_CLOCK, F_OK) == 0);
153 	if (init && !sleep_mode) {
154 		init = False;
155 		if (!wall_clock)
156 			return 0;
157 	}
158 
159 	tzset();
160 
161 	len = sizeof(kern_offset);
162 	if (sysctlbyname("machdep.adjkerntz", &kern_offset, &len, NULL, 0) == -1) {
163 		if (errno == EPERM && !init && geteuid() == 0)
164 			/*
165 			 * Surplus call from jailed root crontab.
166 			 * Avoid spamming logs.
167 			 */
168 			return 1;
169 		syslog(LOG_ERR, "sysctl(\"machdep.adjkerntz\"): %m");
170 		return 1;
171 	}
172 
173 /****** Critical section, do all things as fast as possible ******/
174 
175 	/* get local CMOS clock and possible kernel offset */
176 	if (gettimeofday(&tv, &tz)) {
177 		syslog(LOG_ERR, "gettimeofday: %m");
178 		return 1;
179 	}
180 
181 	/* get the actual local timezone difference */
182 	initial_sec = tv.tv_sec;
183 
184 recalculate:
185 	local = *localtime(&initial_sec);
186 	if (diff == 0)
187 		initial_isdst = local.tm_isdst;
188 	local.tm_isdst = initial_isdst;
189 
190 	/* calculate local CMOS diff from GMT */
191 
192 	localsec = mktime(&local);
193 	if (localsec == -1) {
194 		/*
195 		 * XXX user can only control local time, and it is
196 		 * unacceptable to fail here for init.  2:30 am in the
197 		 * middle of the nonexistent hour means 3:30 am.
198 		 */
199 		if (!sleep_mode) {
200 			syslog(LOG_WARNING,
201 			"Warning: nonexistent local time, try to run later.");
202 			syslog(LOG_WARNING, "Giving up.");
203 			return 1;
204 		}
205 		syslog(LOG_WARNING,
206 			"Warning: nonexistent local time.");
207 		syslog(LOG_WARNING, "Will retry after %d minutes.",
208 			REPORT_PERIOD / 60);
209 		(void) signal(SIGTERM, SIG_DFL);
210 		(void) sigprocmask(SIG_UNBLOCK, &mask, NULL);
211 		(void) sleep(REPORT_PERIOD);
212 		goto again;
213 	}
214 	offset = -local.tm_gmtoff;
215 #ifdef DEBUG
216 	fprintf(stderr, "Initial offset: %ld secs\n", offset);
217 #endif
218 
219 	/* correct the kerneltime for this diffs */
220 	/* subtract kernel offset, if present, old offset too */
221 
222 	diff = offset - tz.tz_minuteswest * 60 - kern_offset;
223 
224 	if (diff != 0) {
225 #ifdef DEBUG
226 		fprintf(stderr, "Initial diff: %ld secs\n", diff);
227 #endif
228 		/* Yet one step for final time */
229 
230 		final_sec = initial_sec + diff;
231 
232 		/* get the actual local timezone difference */
233 		local = *localtime(&final_sec);
234 		final_isdst = diff < 0 ? initial_isdst : local.tm_isdst;
235 		if (diff > 0 && initial_isdst != final_isdst) {
236 			if (looping)
237 				goto bad_final;
238 			looping = True;
239 			initial_isdst = final_isdst;
240 			goto recalculate;
241 		}
242 		local.tm_isdst =  final_isdst;
243 
244 		localsec = mktime(&local);
245 		if (localsec == -1) {
246 		bad_final:
247 			/*
248 			 * XXX as above.  The user has even less control,
249 			 * but perhaps we never get here.
250 			 */
251 			if (!sleep_mode) {
252 				syslog(LOG_WARNING,
253 					"Warning: nonexistent final local time, try to run later.");
254 				syslog(LOG_WARNING, "Giving up.");
255 				return 1;
256 			}
257 			syslog(LOG_WARNING,
258 				"Warning: nonexistent final local time.");
259 			syslog(LOG_WARNING, "Will retry after %d minutes.",
260 				REPORT_PERIOD / 60);
261 			(void) signal(SIGTERM, SIG_DFL);
262 			(void) sigprocmask(SIG_UNBLOCK, &mask, NULL);
263 			(void) sleep(REPORT_PERIOD);
264 			goto again;
265 		}
266 		offset = -local.tm_gmtoff;
267 #ifdef DEBUG
268 		fprintf(stderr, "Final offset: %ld secs\n", offset);
269 #endif
270 
271 		/* correct the kerneltime for this diffs */
272 		/* subtract kernel offset, if present, old offset too */
273 
274 		diff = offset - tz.tz_minuteswest * 60 - kern_offset;
275 
276 		if (diff != 0) {
277 #ifdef DEBUG
278 			fprintf(stderr, "Final diff: %ld secs\n", diff);
279 #endif
280 			/*
281 			 * stv is abused as a flag.  The important value
282 			 * is in `diff'.
283 			 */
284 			stv = &tv;
285 		}
286 	}
287 
288 	if (tz.tz_dsttime != 0 || tz.tz_minuteswest != 0) {
289 		tz.tz_dsttime = tz.tz_minuteswest = 0;  /* zone info is garbage */
290 		stz = &tz;
291 	}
292 	if (!wall_clock && stz == NULL)
293 		stv = NULL;
294 
295 	/* if init or UTC clock and offset/date will be changed, */
296 	/* disable RTC modification for a while.                      */
297 
298 	if (   (init && stv != NULL)
299 	    || ((init || !wall_clock) && kern_offset != offset)
300 	   ) {
301 		len = sizeof(disrtcset);
302 		if (sysctlbyname("machdep.disable_rtc_set", &disrtcset, &len, NULL, 0) == -1) {
303 			syslog(LOG_ERR, "sysctl(get: \"machdep.disable_rtc_set\"): %m");
304 			return 1;
305 		}
306 		if (disrtcset == 0) {
307 			disrtcset = 1;
308 			need_restore = True;
309 			if (sysctlbyname("machdep.disable_rtc_set", NULL, NULL, &disrtcset, len) == -1) {
310 				syslog(LOG_ERR, "sysctl(set: \"machdep.disable_rtc_set\"): %m");
311 				return 1;
312 			}
313 		}
314 	}
315 
316 	if (   (init && (stv != NULL || stz != NULL))
317 	    || (stz != NULL && stv == NULL)
318 	   ) {
319 		if (stv != NULL) {
320 			/*
321 			 * Get the time again, as close as possible to
322 			 * adjusting it, to minimise drift.
323 			 * XXX we'd better not fail between here and
324 			 * restoring disrtcset, since we don't clean up
325 			 * anything.
326 			 */
327 			(void)gettimeofday(&tv, NULL);
328 			tv.tv_sec += diff;
329 			stv = &tv;
330 		}
331 		if (settimeofday(stv, stz)) {
332 			syslog(LOG_ERR, "settimeofday: %m");
333 			return 1;
334 		}
335 	}
336 
337 	/* setting machdep.adjkerntz have a side effect: resettodr(), which */
338 	/* can be disabled by machdep.disable_rtc_set, so if init or UTC clock    */
339 	/* -- don't write RTC, else write RTC.                          */
340 
341 	if (kern_offset != offset) {
342 		kern_offset = offset;
343 		len = sizeof(kern_offset);
344 		if (sysctlbyname("machdep.adjkerntz", NULL, NULL, &kern_offset, len) == -1) {
345 			syslog(LOG_ERR, "sysctl(set: \"machdep.adjkerntz\"): %m");
346 			return 1;
347 		}
348 	}
349 
350 	len = sizeof(wall_clock);
351 	if (sysctlbyname("machdep.wall_cmos_clock",  NULL, NULL, &wall_clock, len) == -1) {
352 		syslog(LOG_ERR, "sysctl(set: \"machdep.wall_cmos_clock\"): %m");
353 		return 1;
354 	}
355 
356 	if (need_restore) {
357 		need_restore = False;
358 		disrtcset = 0;
359 		len = sizeof(disrtcset);
360 		if (sysctlbyname("machdep.disable_rtc_set", NULL, NULL, &disrtcset, len) == -1) {
361 			syslog(LOG_ERR, "sysctl(set: \"machdep.disable_rtc_set\"): %m");
362 			return 1;
363 		}
364 	}
365 
366 /****** End of critical section ******/
367 
368 	if (init && wall_clock) {
369 		sleep_mode = False;
370 		/* wait for signals and acts like -a */
371 		(void) sigsuspend(&emask);
372 		goto again;
373 	}
374 
375 	return 0;
376 }
377 
378 static void
379 usage(void)
380 {
381 	fprintf(stderr, "%s\n%s\n%s\n%s\n",
382 		"usage: adjkerntz -i",
383 		"\t\t(initial call from /etc/rc)",
384 		"       adjkerntz -a [-s]",
385 		"\t\t(adjustment call, -s for sleep/retry mode)");
386 	exit(2);
387 }
388