xref: /freebsd/sbin/adjkerntz/adjkerntz.c (revision 17ee9d00bc1ae1e598c38f25826f861e4bc6c3ce)
1 /*
2  * Copyright (C) 1993, 1994 by Andrew 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 by Andrew A. Chernov, Moscow, Russia.\n\
30  All rights reserved.\n";
31 #endif /* not lint */
32 
33 /*
34  * Andrew 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 disrtcset, need_restore = 0;
76 	sigset_t mask, emask;
77 
78 	while ((ch = getopt(argc, argv, "ai")) != EOF)
79 		switch((char)ch) {
80 		case 'i':               /* initial call, save offset */
81 			if (init != -1)
82 				goto usage;
83 			init = 1;
84 			break;
85 		case 'a':               /* adjustment call, use saved offset */
86 			if (init != -1)
87 				goto usage;
88 			init = 0;
89 			break;
90 		default:
91 		usage:
92 			fprintf(stderr, "Usage:\n\
93 \tadjkerntz -i\t(initial call from /etc/rc)\n\
94 \tadjkerntz -a\t(adjustment call from crontab)\n");
95   			return 2;
96 		}
97 	if (init == -1)
98 		goto usage;
99 
100 	if (access(_PATH_CLOCK, F_OK))
101 		return 0;
102 
103 	sigemptyset(&mask);
104 	sigemptyset(&emask);
105 	sigaddset(&mask, SIGTERM);
106 
107 	openlog("adjkerntz", LOG_PID|LOG_PERROR, LOG_DAEMON);
108 
109 	(void) signal(SIGHUP, SIG_IGN);
110 
111 	if (init && daemon(0, 1)) {
112 		syslog(LOG_ERR, "daemon: %m");
113 		return 1;
114 	}
115 
116 again:
117 
118 	(void) sigprocmask(SIG_BLOCK, &mask, NULL);
119 	(void) signal(SIGTERM, fake);
120 
121 /****** Critical section, do all things as fast as possible ******/
122 
123 	/* get local CMOS clock and possible kernel offset */
124 	if (gettimeofday(&tv, &tz)) {
125 		syslog(LOG_ERR, "gettimeofday: %m");
126 		return 1;
127 	}
128 
129 	/* get the actual local timezone difference */
130 	initial_sec = tv.tv_sec;
131 	local = *localtime(&initial_sec);
132 	utc = *gmtime(&initial_sec);
133 
134 	/* calculate local CMOS diff from GMT */
135 
136 	utcsec = timelocal(&utc);
137 	localsec = timelocal(&local);
138 	if (utcsec == -1 || localsec == -1) {
139 		/*
140 		 * XXX user can only control local time, and it is
141 		 * unacceptable to fail here for init.  2:30 am in the
142 		 * middle of the nonexistent hour means 3:30 am.
143 		 */
144 		syslog(LOG_WARNING,
145 		"Nonexistent local time -- will retry after %d secs", REPORT_PERIOD);
146 		(void) signal(SIGTERM, SIG_DFL);
147 		(void) sigprocmask(SIG_UNBLOCK, &mask, NULL);
148 		(void) sleep(REPORT_PERIOD);
149 		goto again;
150 	}
151 	offset = utcsec - localsec;
152 #ifdef DEBUG
153 	fprintf(stderr, "Initial offset: %ld secs\n", offset);
154 #endif
155 
156 	mib[0] = CTL_MACHDEP;
157 	mib[1] = CPU_ADJKERNTZ;
158 	len = sizeof(kern_offset);
159 	if (sysctl(mib, 2, &kern_offset, &len, NULL, 0) == -1) {
160 		syslog(LOG_ERR, "sysctl(get_offset): %m");
161 		return 1;
162 	}
163 
164 	stv = NULL;
165 	stz = NULL;
166 
167 	/* correct the kerneltime for this diffs */
168 	/* subtract kernel offset, if present, old offset too */
169 
170 	diff = offset - tz.tz_minuteswest * 60 - kern_offset;
171 
172 	if (diff != 0) {
173 #ifdef DEBUG
174 		fprintf(stderr, "Initial diff: %ld secs\n", diff);
175 #endif
176 		/* Yet one step for final time */
177 
178 		final_sec = tv.tv_sec + diff;
179 
180 		/* get the actual local timezone difference */
181 		local = *localtime(&final_sec);
182 		utc = *gmtime(&final_sec);
183 
184 		utcsec = timelocal(&utc);
185 		localsec = timelocal(&local);
186 		if (utcsec == -1 || localsec == -1) {
187 			/*
188 			 * XXX as above.  The user has even less control,
189 			 * but perhaps we never get here.
190 			 */
191 			syslog(LOG_WARNING,
192 		"Nonexistent (final) local time -- will retry after %d secs", REPORT_PERIOD);
193 			(void) signal(SIGTERM, SIG_DFL);
194 			(void) sigprocmask(SIG_UNBLOCK, &mask, NULL);
195 			(void) sleep(REPORT_PERIOD);
196 			goto again;
197 		}
198 		offset = utcsec - localsec;
199 #ifdef DEBUG
200 		fprintf(stderr, "Final offset: %ld secs\n", offset);
201 #endif
202 
203 		/* correct the kerneltime for this diffs */
204 		/* subtract kernel offset, if present, old offset too */
205 
206 		diff = offset - tz.tz_minuteswest * 60 - kern_offset;
207 
208 		if (diff != 0) {
209 #ifdef DEBUG
210 			fprintf(stderr, "Final diff: %ld secs\n", diff);
211 #endif
212 			tv.tv_sec += diff;
213 			tv.tv_usec = 0;       /* we are restarting here... */
214 			stv = &tv;
215 		}
216 	}
217 
218 	if (tz.tz_dsttime != 0 || tz.tz_minuteswest != 0) {
219 		tz.tz_dsttime = tz.tz_minuteswest = 0;  /* zone info is garbage */
220 		stz = &tz;
221 	}
222 
223 	/* if init and something will be changed, don't touch RTC at all */
224 	if (init && (stv != NULL || kern_offset != offset)) {
225 		mib[0] = CTL_MACHDEP;
226 		mib[1] = CPU_DISRTCSET;
227 		len = sizeof(disrtcset);
228 		if (sysctl(mib, 2, &disrtcset, &len, NULL, 0) == -1) {
229 			syslog(LOG_ERR, "sysctl(get_disrtcset): %m");
230 			return 1;
231 		}
232 		if (disrtcset == 0) {
233 			disrtcset = 1;
234 			need_restore = 1;
235 			if (sysctl(mib, 2, NULL, NULL, &disrtcset, len) == -1) {
236 				syslog(LOG_ERR, "sysctl(set_disrtcset): %m");
237 				return 1;
238 			}
239 		}
240 	}
241 
242 	if ((   (init && (stv != NULL || stz != NULL))
243 	     || (stz != NULL && stv == NULL)
244 	    )
245 	    && settimeofday(stv, stz)
246 	   ) {
247 		syslog(LOG_ERR, "settimeofday: %m");
248 		return 1;
249 	}
250 
251 	/* init: don't write RTC, !init: write RTC */
252 	if (kern_offset != offset) {
253 		kern_offset = offset;
254 		mib[0] = CTL_MACHDEP;
255 		mib[1] = CPU_ADJKERNTZ;
256 		len = sizeof(kern_offset);
257 		if (sysctl(mib, 2, NULL, NULL, &kern_offset, len) == -1) {
258 			syslog(LOG_ERR, "sysctl(update_offset): %m");
259 			return 1;
260 		}
261 	}
262 
263 	if (need_restore) {
264 		need_restore = 0;
265 		mib[0] = CTL_MACHDEP;
266 		mib[1] = CPU_DISRTCSET;
267 		disrtcset = 0;
268 		len = sizeof(disrtcset);
269 		if (sysctl(mib, 2, NULL, NULL, &disrtcset, len) == -1) {
270 			syslog(LOG_ERR, "sysctl(restore_disrtcset): %m");
271 			return 1;
272 		}
273 	}
274 
275 /****** End of critical section ******/
276 
277 	if (init) {
278 		init = 0;
279 		/* wait for signals and acts like -a */
280 		(void) sigsuspend(&emask);
281 		goto again;
282 	}
283 
284 	return 0;
285 }
286