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