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