adjkerntz.c revision 4039
1226584Sdim/*
2226584Sdim * Copyright (C) 1993 by Andrew A. Chernov, Moscow, Russia.
3226584Sdim * All rights reserved.
4226584Sdim *
5226584Sdim * Redistribution and use in source and binary forms, with or without
6226584Sdim * modification, are permitted provided that the following conditions
7226584Sdim * are met:
8226584Sdim * 1. Redistributions of source code must retain the above copyright
9226584Sdim *    notice, this list of conditions and the following disclaimer.
10226584Sdim * 2. Redistributions in binary form must reproduce the above copyright
11226584Sdim *    notice, this list of conditions and the following disclaimer in the
12226584Sdim *    documentation and/or other materials provided with the distribution.
13226584Sdim *
14226584Sdim * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND
15226584Sdim * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
16226584Sdim * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
17226584Sdim * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
18226584Sdim * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
19226584Sdim * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
20226584Sdim * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
21226584Sdim * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
22249423Sdim * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
23226584Sdim * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
24263508Sdim * SUCH DAMAGE.
25249423Sdim */
26226584Sdim
27226584Sdim#ifndef lint
28226584Sdimchar copyright[] =
29226584Sdim"@(#)Copyright (C) 1993 by Andrew A. Chernov, Moscow, Russia.\n\
30226584Sdim All rights reserved.\n";
31226584Sdim#endif /* not lint */
32226584Sdim
33226584Sdim/*
34226584Sdim * Andrew A. Chernov   <ache@astral.msk.su>    Dec 20 1993
35226584Sdim *
36226584Sdim * Fix kernel time value if machine run wall CMOS clock
37226584Sdim * (and /etc/wall_cmos_clock file present)
38226584Sdim * using zoneinfo rules or direct TZ environment variable set.
39226584Sdim * Use Joerg Wunsch idea for seconds accurate offset calculation
40226584Sdim * with Garrett Wollman and Bruce Evans fixes.
41226584Sdim *
42226584Sdim */
43226584Sdim#include <stdio.h>
44226584Sdim#include <stdlib.h>
45263508Sdim#include <unistd.h>
46263508Sdim#include <sys/stat.h>
47226584Sdim#include <sys/time.h>
48226584Sdim#include <sys/param.h>
49263508Sdim#include <machine/cpu.h>
50234353Sdim#include <sys/sysctl.h>
51226584Sdim
52226584Sdim#include "pathnames.h"
53226584Sdim
54263508Sdimint main(argc, argv)
55263508Sdim	int argc;
56263508Sdim	char **argv;
57226584Sdim{
58234353Sdim	struct tm local, utc;
59263508Sdim	struct timeval tv, *stv;
60263508Sdim	struct timezone tz, *stz;
61226584Sdim	int kern_offset;
62263508Sdim	size_t len;
63263508Sdim	int mib[2];
64263508Sdim	/* Avoid time_t here, can be unsigned long or worse */
65263508Sdim	long offset, utcsec, localsec, diff;
66263508Sdim	time_t initial_sec, final_sec;
67263508Sdim	int ch, init = -1, verbose = 0;
68263508Sdim	int disrtcset, need_restore = 0;
69263508Sdim
70226584Sdim	while ((ch = getopt(argc, argv, "aiv")) != EOF)
71226584Sdim		switch((char)ch) {
72226584Sdim		case 'i':               /* initial call, save offset */
73226584Sdim			if (init != -1)
74226584Sdim				goto usage;
75226584Sdim			init = 1;
76226584Sdim			break;
77226584Sdim		case 'a':               /* adjustment call, use saved offset */
78226584Sdim			if (init != -1)
79226584Sdim				goto usage;
80226584Sdim			init = 0;
81226584Sdim			break;
82226584Sdim		case 'v':               /* verbose */
83226584Sdim			verbose = 1;
84263508Sdim			break;
85226584Sdim		default:
86226584Sdim		usage:
87226584Sdim			fprintf(stderr, "Usage:\n\
88234353Sdim\tadjkerntz -i [-v]\t(initial call from /etc/rc)\n\
89234353Sdim\tadjkerntz -a [-v]\t(adjustment call from crontab)\n");
90226584Sdim			return 2;
91226584Sdim		}
92226584Sdim	if (init == -1)
93226584Sdim		goto usage;
94226584Sdim
95226584Sdim	if (access(_PATH_CLOCK, F_OK))
96226584Sdim		return 0;
97226584Sdim
98226584Sdim	/* Restore saved offset */
99226584Sdim
100234353Sdim	mib[0] = CTL_MACHDEP;
101226584Sdim	mib[1] = CPU_ADJKERNTZ;
102234353Sdim	len = sizeof(kern_offset);
103234353Sdim	if (sysctl(mib, 2, &kern_offset, &len, NULL, 0) == -1) {
104226584Sdim		perror("sysctl(get_offset)");
105226584Sdim		return 1;
106241430Sdim	}
107263508Sdim
108241430Sdim/****** Critical section, do all things as fast as possible ******/
109241430Sdim
110226584Sdim	/* get local CMOS clock and possible kernel offset */
111263508Sdim	if (gettimeofday(&tv, &tz)) {
112263508Sdim		perror("gettimeofday");
113226584Sdim		return 1;
114226584Sdim	}
115226584Sdim
116226584Sdim	/* get the actual local timezone difference */
117226584Sdim	initial_sec = tv.tv_sec;
118234353Sdim	local = *localtime(&initial_sec);
119234353Sdim	utc = *gmtime(&initial_sec);
120226584Sdim	utc.tm_isdst = local.tm_isdst; /* Use current timezone for mktime(), */
121226584Sdim				       /* because it assumed local time */
122239462Sdim
123226584Sdim	/* calculate local CMOS diff from GMT */
124226584Sdim
125226584Sdim	utcsec = mktime(&utc);
126226584Sdim	localsec = mktime(&local);
127226584Sdim	if (utcsec == -1 || localsec == -1) {
128226584Sdim		/*
129226584Sdim		 * XXX user can only control local time, and it is
130226584Sdim		 * unacceptable to fail here for -i.  2:30 am in the
131226584Sdim		 * middle of the nonexistent hour means 3:30 am.
132226584Sdim		 */
133226584Sdim		fprintf(stderr,
134226584Sdim			"Nonexistent local time - try again in an hour\n");
135226584Sdim		return 1;
136226584Sdim	}
137226584Sdim	offset = utcsec - localsec;
138234353Sdim
139226584Sdim	/* correct the kerneltime for this diffs */
140226584Sdim	/* subtract kernel offset, if present, old offset too */
141226584Sdim
142226584Sdim	diff = offset - tz.tz_minuteswest * 60 - kern_offset;
143263508Sdim
144263508Sdim	if (diff != 0) {
145263508Sdim
146263508Sdim		/* Yet one step for final time */
147263508Sdim
148263508Sdim		final_sec = tv.tv_sec + diff;
149263508Sdim
150263508Sdim		/* get the actual local timezone difference */
151226584Sdim		local = *localtime(&final_sec);
152226584Sdim		utc = *gmtime(&final_sec);
153226584Sdim		utc.tm_isdst = local.tm_isdst; /* Use current timezone for mktime(), */
154226584Sdim					       /* because it assumed local time */
155226584Sdim
156226584Sdim		utcsec = mktime(&utc);
157226584Sdim		localsec = mktime(&local);
158226584Sdim		if (utcsec == -1 || localsec == -1) {
159226584Sdim			/*
160226584Sdim			 * XXX as above.  The user has even less control,
161226584Sdim			 * but perhaps we never get here.
162226584Sdim			 */
163226584Sdim			fprintf(stderr,
164226584Sdim		"Nonexistent (final) local time - try again in an hour\n");
165226584Sdim			return 1;
166226584Sdim		}
167226584Sdim		offset = utcsec - localsec;
168226584Sdim
169226584Sdim		/* correct the kerneltime for this diffs */
170226584Sdim		/* subtract kernel offset, if present, old offset too */
171226584Sdim
172226584Sdim		diff = offset - tz.tz_minuteswest * 60 - kern_offset;
173226584Sdim
174234353Sdim		if (diff != 0) {
175234353Sdim			tv.tv_sec += diff;
176226584Sdim			tv.tv_usec = 0;       /* we are restarting here... */
177226584Sdim			stv = &tv;
178226584Sdim		}
179226584Sdim		else
180226584Sdim			stv = NULL;
181226584Sdim	}
182226584Sdim	else
183226584Sdim		stv = NULL;
184226584Sdim
185226584Sdim	if (tz.tz_dsttime != 0 || tz.tz_minuteswest != 0) {
186226584Sdim		tz.tz_dsttime = tz.tz_minuteswest = 0;  /* zone info is garbage */
187226584Sdim		stz = &tz;
188226584Sdim	}
189226584Sdim	else
190226584Sdim		stz = NULL;
191226584Sdim
192226584Sdim	if (stz != NULL || stv != NULL) {
193226584Sdim		if (init && stv != NULL) {
194226584Sdim			mib[0] = CTL_MACHDEP;
195226584Sdim			mib[1] = CPU_DISRTCSET;
196226584Sdim			len = sizeof(disrtcset);
197226584Sdim			if (sysctl(mib, 2, &disrtcset, &len, NULL, 0) == -1) {
198226584Sdim				perror("sysctl(get_disrtcset)");
199226584Sdim				return 1;
200226584Sdim			}
201226584Sdim			if (disrtcset == 0) {
202226584Sdim				disrtcset = 1;
203226584Sdim				need_restore = 1;
204226584Sdim				if (sysctl(mib, 2, NULL, NULL, &disrtcset, len) == -1) {
205226584Sdim					perror("sysctl(set_disrtcset)");
206226584Sdim					return 1;
207226584Sdim				}
208226584Sdim			}
209226584Sdim		}
210226584Sdim		if ((init || stv == NULL) && settimeofday(stv, stz)) {
211226584Sdim			perror("settimeofday");
212226584Sdim			return 1;
213226584Sdim		}
214226584Sdim	}
215226584Sdim
216226584Sdim	if (kern_offset != offset) {
217226584Sdim		kern_offset = offset;
218226584Sdim		mib[0] = CTL_MACHDEP;
219226584Sdim		mib[1] = CPU_ADJKERNTZ;
220226584Sdim		len = sizeof(kern_offset);
221226584Sdim		if (sysctl(mib, 2, NULL, NULL, &kern_offset, len) == -1) {
222226584Sdim			perror("sysctl(update_offset)");
223226584Sdim			return 1;
224226584Sdim		}
225226584Sdim	}
226226584Sdim
227226584Sdim	if (need_restore) {
228226584Sdim		disrtcset = 0;
229226584Sdim		if (sysctl(mib, 2, NULL, NULL, &disrtcset, len) == -1) {
230263508Sdim			perror("sysctl(restore_disrtcset)");
231263508Sdim			return 1;
232263508Sdim		}
233263508Sdim	}
234263508Sdim
235263508Sdim/****** End of critical section ******/
236263508Sdim
237263508Sdim	if (verbose)
238226584Sdim		printf("Calculated zone offset difference: %ld seconds\n",
239263508Sdim		       diff);
240263508Sdim
241263508Sdim	return 0;
242226584Sdim}
243226584Sdim
244226584Sdim