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