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