adjkerntz.c revision 3368
133180Sjdp/*
233180Sjdp * Copyright (C) 1993 by Andrew A. Chernov, Moscow, Russia.
333180Sjdp * All rights reserved.
433180Sjdp *
533180Sjdp * Redistribution and use in source and binary forms, with or without
633180Sjdp * modification, are permitted provided that the following conditions
733180Sjdp * are met:
833180Sjdp * 1. Redistributions of source code must retain the above copyright
933180Sjdp *    notice, this list of conditions and the following disclaimer.
1033180Sjdp * 2. Redistributions in binary form must reproduce the above copyright
1133180Sjdp *    notice, this list of conditions and the following disclaimer in the
1233180Sjdp *    documentation and/or other materials provided with the distribution.
1333180Sjdp *
1433180Sjdp * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND
1533180Sjdp * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
1633180Sjdp * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
1733180Sjdp * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
1833180Sjdp * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
1933180Sjdp * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
2033180Sjdp * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
2133180Sjdp * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
2233180Sjdp * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
2333180Sjdp * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
2433180Sjdp * SUCH DAMAGE.
2533180Sjdp */
2633180Sjdp
2790039Sobrien#ifndef lint
2890039Sobrienchar copyright[] =
2990039Sobrien"@(#)Copyright (C) 1993 by Andrew A. Chernov, Moscow, Russia.\n\
3033180Sjdp All rights reserved.\n";
31103436Speter#endif /* not lint */
3233180Sjdp
3334196Sjdp/*
34168314Skan * Andrew A. Chernov   <ache@astral.msk.su>    Dec 20 1993
3534196Sjdp *
3634196Sjdp * Fix kernel time value if machine run wall CMOS clock
37205606Sgahr * (and /etc/wall_cmos_clock file present)
3834196Sjdp * using zoneinfo rules or direct TZ environment variable set.
3934196Sjdp * Use Joerg Wunsch idea for seconds accurate offset calculation
4034196Sjdp * with Garrett Wollman and Bruce Evans fixes.
4134196Sjdp *
4234196Sjdp */
4334196Sjdp#include <stdio.h>
4434196Sjdp#include <stdlib.h>
4534196Sjdp#include <unistd.h>
4634196Sjdp#include <sys/stat.h>
4734196Sjdp#include <sys/time.h>
4834196Sjdp#include <sys/param.h>
4934196Sjdp#include <machine/cpu.h>
5034196Sjdp#include <sys/sysctl.h>
5134196Sjdp
5234196Sjdp#include "pathnames.h"
5334196Sjdp
5434196Sjdpint main(argc, argv)
5534196Sjdp	int argc;
5634196Sjdp	char **argv;
5734196Sjdp{
5834196Sjdp	struct tm local, utc;
5934196Sjdp	struct timeval tv, *stv;
6034196Sjdp	struct timezone tz, *stz;
6134196Sjdp	int kern_offset;
6234196Sjdp	size_t len;
6334196Sjdp	int mib[2];
6434196Sjdp	/* Avoid time_t here, can be unsigned long or worse */
6534196Sjdp	long offset, utcsec, localsec, diff;
6634196Sjdp	time_t initial_sec, final_sec;
6734196Sjdp	int ch, init = -1, verbose = 0;
6834196Sjdp
6934196Sjdp	while ((ch = getopt(argc, argv, "aiv")) != EOF)
7034196Sjdp		switch((char)ch) {
7134196Sjdp		case 'i':               /* initial call, save offset */
72205606Sgahr			if (init != -1)
7334196Sjdp				goto usage;
7434196Sjdp			init = 1;
7534196Sjdp			break;
7634196Sjdp		case 'a':               /* adjustment call, use saved offset */
7734196Sjdp			if (init != -1)
7855122Sjdp				goto usage;
7955122Sjdp			init = 0;
8055122Sjdp			break;
8155122Sjdp		case 'v':               /* verbose */
8255122Sjdp			verbose = 1;
8355122Sjdp			break;
8455122Sjdp		default:
8555122Sjdp		usage:
8655122Sjdp			fprintf(stderr, "Usage:\n\
8755122Sjdp\tadjkerntz -i [-v]\t(initial call from /etc/rc)\n\
8855122Sjdp\tadjkerntz -a [-v]\t(adjustment call from crontab)\n");
8955122Sjdp			return 2;
9055122Sjdp		}
9155122Sjdp	if (init == -1)
9234196Sjdp		goto usage;
9334196Sjdp
9434196Sjdp	if (access(_PATH_CLOCK, F_OK))
9534196Sjdp		return 0;
9634196Sjdp
9734196Sjdp	/* Restore saved offset */
9834196Sjdp
9934196Sjdp	mib[0] = CTL_MACHDEP;
10034196Sjdp	mib[1] = CPU_ADJKERNTZ;
10134196Sjdp	len = sizeof(kern_offset);
102103213Smike	if (sysctl(mib, 2, &kern_offset, &len, NULL, 0) == -1) {
10334196Sjdp		perror("sysctl(get_offset)");
10434196Sjdp		return 1;
10534196Sjdp	}
10634196Sjdp
107110804Skan/****** Critical section, do all things as fast as possible ******/
108190673Skib
109190673Skib	/* get local CMOS clock and possible kernel offset */
110190673Skib	if (gettimeofday(&tv, &tz)) {
111190673Skib		perror("gettimeofday");
112190673Skib		return 1;
113190673Skib	}
114190673Skib
115190673Skib	/* get the actual local timezone difference */
116153515Skan	initial_sec = tv.tv_sec;
117153515Skan	local = *localtime(&initial_sec);
118153515Skan	utc = *gmtime(&initial_sec);
119153515Skan	utc.tm_isdst = local.tm_isdst; /* Use current timezone for mktime(), */
120153515Skan				       /* because it assumed local time */
121153515Skan
122153515Skan	/* calculate local CMOS diff from GMT */
123153515Skan
124153515Skan	utcsec = mktime(&utc);
125110804Skan	localsec = mktime(&local);
126110804Skan	if (utcsec == -1 || localsec == -1) {
127110804Skan		/*
128110804Skan		 * XXX user can only control local time, and it is
129110804Skan		 * unacceptable to fail here for -i.  2:30 am in the
130126643Smarkm		 * middle of the nonexistent hour means 3:30 am.
131110804Skan		 */
132115401Skan		fprintf(stderr,
133115401Skan			"Nonexistent local time - try again in an hour\n");
134115401Skan		return 1;
135115401Skan	}
136115401Skan	offset = utcsec - localsec;
137115401Skan
138115401Skan	/* correct the kerneltime for this diffs */
139168314Skan	/* subtract kernel offset, if present, old offset too */
140168314Skan
141168314Skan	diff = offset - tz.tz_minuteswest * 60 - kern_offset;
142168314Skan
143168314Skan	if (diff != 0) {
144168314Skan
145168314Skan		/* Yet one step for final time */
146168314Skan
147168314Skan		final_sec = tv.tv_sec + diff;
148185369Skib
149185369Skib		/* get the actual local timezone difference */
150185369Skib		local = *localtime(&final_sec);
151185369Skib		utc = *gmtime(&final_sec);
152185369Skib		utc.tm_isdst = local.tm_isdst; /* Use current timezone for mktime(), */
153185369Skib					       /* because it assumed local time */
154185369Skib
155185369Skib		utcsec = mktime(&utc);
156185369Skib		localsec = mktime(&local);
157185369Skib		if (utcsec == -1 || localsec == -1) {
158185369Skib			/*
159185369Skib			 * XXX as above.  The user has even less control,
160			 * but perhaps we never get here.
161			 */
162			fprintf(stderr,
163		"Nonexistent (final) local time - try again in an hour\n");
164			return 1;
165		}
166		offset = utcsec - localsec;
167
168		/* correct the kerneltime for this diffs */
169		/* subtract kernel offset, if present, old offset too */
170
171		diff = offset - tz.tz_minuteswest * 60 - kern_offset;
172
173		if (diff != 0) {
174			tv.tv_sec += diff;
175			tv.tv_usec = 0;       /* we are restarting here... */
176			stv = &tv;
177		}
178		else
179			stv = NULL;
180	}
181	else
182		stv = NULL;
183
184	if (kern_offset != offset) {
185		kern_offset = offset;
186		mib[0] = CTL_MACHDEP;
187		mib[1] = CPU_ADJKERNTZ;
188		len = sizeof(kern_offset);
189		if (sysctl(mib, 2, NULL, NULL, &kern_offset, len) == -1) {
190			perror("sysctl(update_offset)");
191			return 1;
192		}
193	}
194
195	if (tz.tz_dsttime != 0 || tz.tz_minuteswest != 0) {
196		tz.tz_dsttime = tz.tz_minuteswest = 0;  /* zone info is garbage */
197		stz = &tz;
198	}
199	else
200		stz = NULL;
201
202	if (stz != NULL || stv != NULL) {
203		int disrtcset, need_restore = 0;
204
205		if (init && stv != NULL) {
206			mib[0] = CTL_MACHDEP;
207			mib[1] = CPU_DISRTCSET;
208			len = sizeof(disrtcset);
209			if (sysctl(mib, 2, &disrtcset, &len, NULL, 0) == -1) {
210				perror("sysctl(get_disrtcset)");
211				return 1;
212			}
213			if (disrtcset == 0) {
214				disrtcset = 1;
215				need_restore = 1;
216				if (sysctl(mib, 2, NULL, NULL, &disrtcset, len) == -1) {
217					perror("sysctl(set_disrtcset)");
218					return 1;
219				}
220			}
221		}
222		if (settimeofday(stv, stz)) {
223			perror("settimeofday");
224			return 1;
225		}
226		if (need_restore) {
227			disrtcset = 0;
228			if (sysctl(mib, 2, NULL, NULL, &disrtcset, len) == -1) {
229				perror("sysctl(restore_disrtcset)");
230				return 1;
231			}
232		}
233	}
234
235/****** End of critical section ******/
236
237	if (verbose)
238		printf("Calculated zone offset difference: %ld seconds\n",
239		       diff);
240
241	return 0;
242}
243
244