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