xref: /freebsd/sbin/adjkerntz/adjkerntz.c (revision 5ebc7e6281887681c3a348a5a4c902e262ccd656)
1 /*
2  * Copyright (C) 1993, 1994, 1995 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 char copyright[] =
29 "@(#)Copyright (C) 1993, 1994, 1995 by Andrey A. Chernov, Moscow, Russia.\n\
30  All rights reserved.\n";
31 #endif /* not lint */
32 
33 /*
34  * Andrey A. Chernov   <ache@astral.msk.su>    Dec 20 1993
35  *
36  * Fix kernel time value if machine run wall CMOS clock
37  * (and /etc/wall_cmos_clock file present)
38  * using zoneinfo rules or direct TZ environment variable set.
39  * Use Joerg Wunsch idea for seconds accurate offset calculation
40  * with Garrett Wollman and Bruce Evans fixes.
41  *
42  */
43 #include <stdio.h>
44 #include <signal.h>
45 #include <stdlib.h>
46 #include <unistd.h>
47 #include <syslog.h>
48 #include <sys/stat.h>
49 #include <sys/time.h>
50 #include <sys/param.h>
51 #include <machine/cpu.h>
52 #include <sys/sysctl.h>
53 
54 #include "pathnames.h"
55 
56 /*#define DEBUG*/
57 #define REPORT_PERIOD (30*60)
58 
59 void fake() {}
60 
61 int main(argc, argv)
62 	int argc;
63 	char **argv;
64 {
65 	struct tm local, utc;
66 	struct timeval tv, *stv;
67 	struct timezone tz, *stz;
68 	int kern_offset;
69 	size_t len;
70 	int mib[2];
71 	/* Avoid time_t here, can be unsigned long or worse */
72 	long offset, utcsec, localsec, diff;
73 	time_t initial_sec, final_sec;
74 	int ch, init = -1;
75 	int initial_isdst = -1, final_isdst, looping;
76 	int disrtcset, need_restore = 0;
77 	sigset_t mask, emask;
78 
79 	while ((ch = getopt(argc, argv, "ai")) != EOF)
80 		switch((char)ch) {
81 		case 'i':               /* initial call, save offset */
82 			if (init != -1)
83 				goto usage;
84 			init = 1;
85 			break;
86 		case 'a':               /* adjustment call, use saved offset */
87 			if (init != -1)
88 				goto usage;
89 			init = 0;
90 			break;
91 		default:
92 		usage:
93 			fprintf(stderr, "Usage:\n\
94 \tadjkerntz -i\t(initial call from /etc/rc)\n\
95 \tadjkerntz -a\t(adjustment call from crontab)\n");
96   			return 2;
97 		}
98 	if (init == -1)
99 		goto usage;
100 
101 	if (access(_PATH_CLOCK, F_OK))
102 		return 0;
103 
104 	sigemptyset(&mask);
105 	sigemptyset(&emask);
106 	sigaddset(&mask, SIGTERM);
107 
108 	openlog("adjkerntz", LOG_PID|LOG_PERROR, LOG_DAEMON);
109 
110 	(void) signal(SIGHUP, SIG_IGN);
111 
112 	if (init && daemon(0, 1)) {
113 		syslog(LOG_ERR, "daemon: %m");
114 		return 1;
115 	}
116 
117 again:
118 
119 	(void) sigprocmask(SIG_BLOCK, &mask, NULL);
120 	(void) signal(SIGTERM, fake);
121 
122 	diff = 0;
123 	stv = NULL;
124 	stz = NULL;
125 	looping = 0;
126 
127 	mib[0] = CTL_MACHDEP;
128 	mib[1] = CPU_ADJKERNTZ;
129 	len = sizeof(kern_offset);
130 	if (sysctl(mib, 2, &kern_offset, &len, NULL, 0) == -1) {
131 		syslog(LOG_ERR, "sysctl(get_offset): %m");
132 		return 1;
133 	}
134 
135 /****** Critical section, do all things as fast as possible ******/
136 
137 	/* get local CMOS clock and possible kernel offset */
138 	if (gettimeofday(&tv, &tz)) {
139 		syslog(LOG_ERR, "gettimeofday: %m");
140 		return 1;
141 	}
142 
143 	/* get the actual local timezone difference */
144 	initial_sec = tv.tv_sec;
145 
146 recalculate:
147 	local = *localtime(&initial_sec);
148 	if (diff == 0)
149 		initial_isdst = local.tm_isdst;
150 	utc = *gmtime(&initial_sec);
151 	local.tm_isdst = utc.tm_isdst = initial_isdst;
152 
153 	/* calculate local CMOS diff from GMT */
154 
155 	utcsec = mktime(&utc);
156 	localsec = mktime(&local);
157 	if (utcsec == -1 || localsec == -1) {
158 		/*
159 		 * XXX user can only control local time, and it is
160 		 * unacceptable to fail here for init.  2:30 am in the
161 		 * middle of the nonexistent hour means 3:30 am.
162 		 */
163 		syslog(LOG_WARNING,
164 		"Nonexistent local time -- will retry after %d secs", REPORT_PERIOD);
165 		(void) signal(SIGTERM, SIG_DFL);
166 		(void) sigprocmask(SIG_UNBLOCK, &mask, NULL);
167 		(void) sleep(REPORT_PERIOD);
168 		goto again;
169 	}
170 	offset = utcsec - localsec;
171 #ifdef DEBUG
172 	fprintf(stderr, "Initial offset: %ld secs\n", offset);
173 #endif
174 
175 	/* correct the kerneltime for this diffs */
176 	/* subtract kernel offset, if present, old offset too */
177 
178 	diff = offset - tz.tz_minuteswest * 60 - kern_offset;
179 
180 	if (diff != 0) {
181 #ifdef DEBUG
182 		fprintf(stderr, "Initial diff: %ld secs\n", diff);
183 #endif
184 		/* Yet one step for final time */
185 
186 		final_sec = initial_sec + diff;
187 
188 		/* get the actual local timezone difference */
189 		local = *localtime(&final_sec);
190 		final_isdst = diff < 0 ? initial_isdst : local.tm_isdst;
191 		if (diff > 0 && initial_isdst != final_isdst) {
192 			if (looping)
193 				goto bad_final;
194 			looping++;
195 			initial_isdst = final_isdst;
196 			goto recalculate;
197 		}
198 		utc = *gmtime(&final_sec);
199 		local.tm_isdst = utc.tm_isdst = final_isdst;
200 
201 		utcsec = mktime(&utc);
202 		localsec = mktime(&local);
203 		if (utcsec == -1 || localsec == -1) {
204 		bad_final:
205 			/*
206 			 * XXX as above.  The user has even less control,
207 			 * but perhaps we never get here.
208 			 */
209 			syslog(LOG_WARNING,
210 		"Nonexistent (final) local time -- will retry after %d secs", REPORT_PERIOD);
211 			(void) signal(SIGTERM, SIG_DFL);
212 			(void) sigprocmask(SIG_UNBLOCK, &mask, NULL);
213 			(void) sleep(REPORT_PERIOD);
214 			goto again;
215 		}
216 		offset = utcsec - localsec;
217 #ifdef DEBUG
218 		fprintf(stderr, "Final offset: %ld secs\n", offset);
219 #endif
220 
221 		/* correct the kerneltime for this diffs */
222 		/* subtract kernel offset, if present, old offset too */
223 
224 		diff = offset - tz.tz_minuteswest * 60 - kern_offset;
225 
226 		if (diff != 0) {
227 #ifdef DEBUG
228 			fprintf(stderr, "Final diff: %ld secs\n", diff);
229 #endif
230 			tv.tv_sec += diff;
231 			tv.tv_usec = 0;       /* we are restarting here... */
232 			stv = &tv;
233 		}
234 	}
235 
236 	if (tz.tz_dsttime != 0 || tz.tz_minuteswest != 0) {
237 		tz.tz_dsttime = tz.tz_minuteswest = 0;  /* zone info is garbage */
238 		stz = &tz;
239 	}
240 
241 	/* if init and something will be changed, don't touch RTC at all */
242 	if (init && (stv != NULL || kern_offset != offset)) {
243 		mib[0] = CTL_MACHDEP;
244 		mib[1] = CPU_DISRTCSET;
245 		len = sizeof(disrtcset);
246 		if (sysctl(mib, 2, &disrtcset, &len, NULL, 0) == -1) {
247 			syslog(LOG_ERR, "sysctl(get_disrtcset): %m");
248 			return 1;
249 		}
250 		if (disrtcset == 0) {
251 			disrtcset = 1;
252 			need_restore = 1;
253 			if (sysctl(mib, 2, NULL, NULL, &disrtcset, len) == -1) {
254 				syslog(LOG_ERR, "sysctl(set_disrtcset): %m");
255 				return 1;
256 			}
257 		}
258 	}
259 
260 	if ((   (init && (stv != NULL || stz != NULL))
261 	     || (stz != NULL && stv == NULL)
262 	    )
263 	    && settimeofday(stv, stz)
264 	   ) {
265 		syslog(LOG_ERR, "settimeofday: %m");
266 		return 1;
267 	}
268 
269 	/* init: don't write RTC, !init: write RTC */
270 	if (kern_offset != offset) {
271 		kern_offset = offset;
272 		mib[0] = CTL_MACHDEP;
273 		mib[1] = CPU_ADJKERNTZ;
274 		len = sizeof(kern_offset);
275 		if (sysctl(mib, 2, NULL, NULL, &kern_offset, len) == -1) {
276 			syslog(LOG_ERR, "sysctl(update_offset): %m");
277 			return 1;
278 		}
279 	}
280 
281 	if (need_restore) {
282 		need_restore = 0;
283 		mib[0] = CTL_MACHDEP;
284 		mib[1] = CPU_DISRTCSET;
285 		disrtcset = 0;
286 		len = sizeof(disrtcset);
287 		if (sysctl(mib, 2, NULL, NULL, &disrtcset, len) == -1) {
288 			syslog(LOG_ERR, "sysctl(restore_disrtcset): %m");
289 			return 1;
290 		}
291 	}
292 
293 /****** End of critical section ******/
294 
295 	if (init) {
296 		init = 0;
297 		/* wait for signals and acts like -a */
298 		(void) sigsuspend(&emask);
299 		goto again;
300 	}
301 
302 	return 0;
303 }
304