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