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