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