adjkerntz.c revision 24359
121308Sache/* 221308Sache * Copyright (C) 1993-1996 by Andrey A. Chernov, Moscow, Russia. 321308Sache * All rights reserved. 421308Sache * 521308Sache * Redistribution and use in source and binary forms, with or without 621308Sache * modification, are permitted provided that the following conditions 721308Sache * are met: 821308Sache * 1. Redistributions of source code must retain the above copyright 921308Sache * notice, this list of conditions and the following disclaimer. 1021308Sache * 2. Redistributions in binary form must reproduce the above copyright 1121308Sache * notice, this list of conditions and the following disclaimer in the 1221308Sache * documentation and/or other materials provided with the distribution. 1321308Sache * 1421308Sache * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND 1521308Sache * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 1621308Sache * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 1721308Sache * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE 1821308Sache * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 1921308Sache * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 2021308Sache * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 2121308Sache * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 2221308Sache * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 2321308Sache * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 2421308Sache * SUCH DAMAGE. 2521308Sache */ 2621308Sache 2721308Sache#ifndef lint 2821308Sachechar copyright[] = 2921308Sache"@(#)Copyright (C) 1993-1996 by Andrey A. Chernov, Moscow, Russia.\n\ 3021308Sache All rights reserved.\n"; 3121308Sache#endif /* not lint */ 3221308Sache 3321308Sache/* 3421308Sache * Andrey A. Chernov <ache@astral.msk.su> Dec 20 1993 3521308Sache * 3621308Sache * Fix kernel time value if machine run wall CMOS clock 3721308Sache * (and /etc/wall_cmos_clock file present) 3821308Sache * using zoneinfo rules or direct TZ environment variable set. 3921308Sache * Use Joerg Wunsch idea for seconds accurate offset calculation 4021308Sache * with Garrett Wollman and Bruce Evans fixes. 4121308Sache * 4221308Sache */ 4321308Sache#include <stdio.h> 4421308Sache#include <signal.h> 4521308Sache#include <stdlib.h> 4621308Sache#include <unistd.h> 4721308Sache#include <syslog.h> 4821308Sache#include <sys/stat.h> 4921308Sache#include <sys/time.h> 5021308Sache#include <sys/param.h> 5121308Sache#include <machine/cpu.h> 5221308Sache#include <sys/sysctl.h> 5321308Sache 5421308Sache#include "pathnames.h" 5521308Sache 5621308Sache/*#define DEBUG*/ 5721308Sache 5821308Sache#define True (1) 5921308Sache#define False (0) 6021308Sache#define Unknown (-1) 6121308Sache 6221308Sache#define REPORT_PERIOD (30*60) 6321308Sache 6421308Sachevoid fake() {} 6521308Sache 6621308Sacheint main(argc, argv) 6721308Sache int argc; 6821308Sache char **argv; 6921308Sache{ 7021308Sache struct tm local, utc; 7121308Sache struct timeval tv, *stv; 7221308Sache struct timezone tz, *stz; 7321308Sache int kern_offset, wall_clock, disrtcset; 7421308Sache size_t len; 7521308Sache int mib[2]; 7621308Sache /* Avoid time_t here, can be unsigned long or worse */ 7721308Sache long offset, utcsec, localsec, diff; 7821308Sache time_t initial_sec, final_sec; 7921308Sache int ch; 8021308Sache int initial_isdst = -1, final_isdst; 8121308Sache int need_restore = False, sleep_mode = False, looping, 8221308Sache init = Unknown; 8321308Sache sigset_t mask, emask; 8421308Sache 8521308Sache while ((ch = getopt(argc, argv, "ais")) != -1) 8621308Sache switch((char)ch) { 8721308Sache case 'i': /* initial call, save offset */ 8821308Sache if (init != Unknown) 8921308Sache goto usage; 9021308Sache init = True; 9121308Sache break; 9221308Sache case 'a': /* adjustment call, use saved offset */ 9321308Sache if (init != Unknown) 9421308Sache goto usage; 9521308Sache init = False; 9621308Sache break; 9721308Sache case 's': 9821308Sache sleep_mode = True; 9921308Sache break; 10021308Sache default: 10121308Sache usage: 10221308Sache fprintf(stderr, "Usage:\n\ 10321308Sache\tadjkerntz -i\t\t(initial call from /etc/rc)\n\ 10421308Sache\tadjkerntz -a [-s]\t(adjustment call, -s for sleep/retry mode)\n"); 10521308Sache return 2; 10621308Sache } 10721308Sache if (init == Unknown) 10821308Sache goto usage; 10921308Sache if (init) 11021308Sache sleep_mode = True; 11121308Sache 11221308Sache sigemptyset(&mask); 11321308Sache sigemptyset(&emask); 11421308Sache sigaddset(&mask, SIGTERM); 11521308Sache 11621308Sache openlog("adjkerntz", LOG_PID|LOG_PERROR, LOG_DAEMON); 11721308Sache 11821308Sache (void) signal(SIGHUP, SIG_IGN); 11921308Sache 12021308Sache if (init && daemon(0, 1)) { 12121308Sache syslog(LOG_ERR, "daemon: %m"); 12221308Sache return 1; 12321308Sache } 12421308Sache 12521308Sacheagain: 12621308Sache (void) sigprocmask(SIG_BLOCK, &mask, NULL); 12721308Sache (void) signal(SIGTERM, fake); 12821308Sache 12921308Sache diff = 0; 13021308Sache stv = NULL; 13121308Sache stz = NULL; 13221308Sache looping = False; 13321308Sache 13421308Sache wall_clock = (access(_PATH_CLOCK, F_OK) == 0); 13521308Sache if (init && !sleep_mode) { 13621308Sache init = False; 13721308Sache if (!wall_clock) 13821308Sache return 0; 13921308Sache } 14021308Sache 14121308Sache mib[0] = CTL_MACHDEP; 14221308Sache mib[1] = CPU_ADJKERNTZ; 14321308Sache len = sizeof(kern_offset); 14421308Sache if (sysctl(mib, 2, &kern_offset, &len, NULL, 0) == -1) { 14521308Sache syslog(LOG_ERR, "sysctl(get_offset): %m"); 14621308Sache return 1; 14721308Sache } 14821308Sache 14921308Sache/****** Critical section, do all things as fast as possible ******/ 15021308Sache 15121308Sache /* get local CMOS clock and possible kernel offset */ 15221308Sache if (gettimeofday(&tv, &tz)) { 15321308Sache syslog(LOG_ERR, "gettimeofday: %m"); 15421308Sache return 1; 15521308Sache } 15621308Sache 15721308Sache /* get the actual local timezone difference */ 15821308Sache initial_sec = tv.tv_sec; 15921308Sache 16021308Sacherecalculate: 16121308Sache local = *localtime(&initial_sec); 16221308Sache if (diff == 0) 16321308Sache initial_isdst = local.tm_isdst; 16421308Sache utc = *gmtime(&initial_sec); 16521308Sache local.tm_isdst = utc.tm_isdst = initial_isdst; 16621308Sache 16721308Sache /* calculate local CMOS diff from GMT */ 16821308Sache 16921308Sache utcsec = mktime(&utc); 17021308Sache localsec = mktime(&local); 17121308Sache if (utcsec == -1 || localsec == -1) { 17221308Sache /* 17321308Sache * XXX user can only control local time, and it is 17421308Sache * unacceptable to fail here for init. 2:30 am in the 17521308Sache * middle of the nonexistent hour means 3:30 am. 17621308Sache */ 17721308Sache syslog(LOG_WARNING, 17821308Sache "Warning: nonexistent %s time.", 17921308Sache utcsec == -1 && localsec == -1 ? "UTC time and local" : 18021308Sache utcsec == -1 ? "UTC" : "local"); 18121308Sache if (!sleep_mode) { 18221308Sache syslog(LOG_WARNING, "Giving up."); 18321308Sache return 1; 18421308Sache } 18521308Sache syslog(LOG_WARNING, "Will retry after %d minutes.", 18621308Sache REPORT_PERIOD / 60); 18721308Sache (void) signal(SIGTERM, SIG_DFL); 18821308Sache (void) sigprocmask(SIG_UNBLOCK, &mask, NULL); 18921308Sache (void) sleep(REPORT_PERIOD); 19021308Sache goto again; 19121308Sache } 19221308Sache offset = utcsec - localsec; 19321308Sache#ifdef DEBUG 19421308Sache fprintf(stderr, "Initial offset: %ld secs\n", offset); 19521308Sache#endif 19621308Sache 19721308Sache /* correct the kerneltime for this diffs */ 19821308Sache /* subtract kernel offset, if present, old offset too */ 19921308Sache 20021308Sache diff = offset - tz.tz_minuteswest * 60 - kern_offset; 20121308Sache 20221308Sache if (diff != 0) { 20321308Sache#ifdef DEBUG 20421308Sache fprintf(stderr, "Initial diff: %ld secs\n", diff); 20521308Sache#endif 20621308Sache /* Yet one step for final time */ 20721308Sache 20821308Sache final_sec = initial_sec + diff; 20921308Sache 21021308Sache /* get the actual local timezone difference */ 21121308Sache local = *localtime(&final_sec); 21221308Sache final_isdst = diff < 0 ? initial_isdst : local.tm_isdst; 21321308Sache if (diff > 0 && initial_isdst != final_isdst) { 21421308Sache if (looping) 21521308Sache goto bad_final; 21621308Sache looping = True; 21721308Sache initial_isdst = final_isdst; 21821308Sache goto recalculate; 21921308Sache } 22021308Sache utc = *gmtime(&final_sec); 22121308Sache local.tm_isdst = utc.tm_isdst = final_isdst; 22221308Sache 22321308Sache utcsec = mktime(&utc); 22421308Sache localsec = mktime(&local); 22521308Sache if (utcsec == -1 || localsec == -1) { 22621308Sache bad_final: 22721308Sache /* 22821308Sache * XXX as above. The user has even less control, 22921308Sache * but perhaps we never get here. 23021308Sache */ 23121308Sache syslog(LOG_WARNING, 23221308Sache "Warning: nonexistent final %s time.", 23321308Sache utcsec == -1 && localsec == -1 ? "UTC time and local" : 23421308Sache utcsec == -1 ? "UTC" : "local"); 23521308Sache if (!sleep_mode) { 23621308Sache syslog(LOG_WARNING, "Giving up."); 23721308Sache return 1; 23821308Sache } 23921308Sache syslog(LOG_WARNING, "Will retry after %d minutes.", 24021308Sache REPORT_PERIOD / 60); 24121308Sache (void) signal(SIGTERM, SIG_DFL); 24221308Sache (void) sigprocmask(SIG_UNBLOCK, &mask, NULL); 24321308Sache (void) sleep(REPORT_PERIOD); 24421308Sache goto again; 24521308Sache } 24621308Sache offset = utcsec - localsec; 24721308Sache#ifdef DEBUG 24821308Sache fprintf(stderr, "Final offset: %ld secs\n", offset); 24921308Sache#endif 25021308Sache 25121308Sache /* correct the kerneltime for this diffs */ 25221308Sache /* subtract kernel offset, if present, old offset too */ 25321308Sache 25421308Sache diff = offset - tz.tz_minuteswest * 60 - kern_offset; 25521308Sache 25621308Sache if (diff != 0) { 25721308Sache#ifdef DEBUG 25821308Sache fprintf(stderr, "Final diff: %ld secs\n", diff); 25921308Sache#endif 26021308Sache tv.tv_sec += diff; 26121308Sache tv.tv_usec = 0; /* we are restarting here... */ 26221308Sache stv = &tv; 26321308Sache } 26421308Sache } 26521308Sache 26621308Sache if (tz.tz_dsttime != 0 || tz.tz_minuteswest != 0) { 26721308Sache tz.tz_dsttime = tz.tz_minuteswest = 0; /* zone info is garbage */ 26821308Sache stz = &tz; 26921308Sache } 27021308Sache if (!wall_clock && stz == NULL) 27121308Sache stv = NULL; 27221308Sache 27321308Sache /* if init or UTC clock and offset/date will be changed, */ 27421308Sache /* disable RTC modification for a while. */ 27521308Sache 27621308Sache if ( (init && stv != NULL) 27721308Sache || ((init || !wall_clock) && kern_offset != offset) 27821308Sache ) { 27921308Sache mib[0] = CTL_MACHDEP; 28021308Sache mib[1] = CPU_DISRTCSET; 28121308Sache len = sizeof(disrtcset); 28221308Sache if (sysctl(mib, 2, &disrtcset, &len, NULL, 0) == -1) { 28321308Sache syslog(LOG_ERR, "sysctl(get_disrtcset): %m"); 28421308Sache return 1; 28521308Sache } 28621308Sache if (disrtcset == 0) { 28721308Sache disrtcset = 1; 28821308Sache need_restore = True; 28921308Sache if (sysctl(mib, 2, NULL, NULL, &disrtcset, len) == -1) { 29021308Sache syslog(LOG_ERR, "sysctl(set_disrtcset): %m"); 29121308Sache return 1; 29221308Sache } 29321308Sache } 29421308Sache } 29521308Sache 29621308Sache if ( ( (init && (stv != NULL || stz != NULL)) 29721308Sache || (stz != NULL && stv == NULL) 29821308Sache ) 29921308Sache && settimeofday(stv, stz) 30021308Sache ) { 30121308Sache syslog(LOG_ERR, "settimeofday: %m"); 30221308Sache return 1; 30321308Sache } 30421308Sache 30521308Sache /* setting CPU_ADJKERNTZ have a side effect: resettodr(), which */ 30621308Sache /* can be disabled by CPU_DISRTCSET, so if init or UTC clock */ 30721308Sache /* -- don't write RTC, else write RTC. */ 30821308Sache 30921308Sache if (kern_offset != offset) { 31021308Sache kern_offset = offset; 31121308Sache mib[0] = CTL_MACHDEP; 31221308Sache mib[1] = CPU_ADJKERNTZ; 31321308Sache len = sizeof(kern_offset); 31421308Sache if (sysctl(mib, 2, NULL, NULL, &kern_offset, len) == -1) { 31521308Sache syslog(LOG_ERR, "sysctl(update_offset): %m"); 31621308Sache return 1; 31721308Sache } 31821308Sache } 31921308Sache 32021308Sache mib[0] = CTL_MACHDEP; 32121308Sache mib[1] = CPU_WALLCLOCK; 32221308Sache len = sizeof(wall_clock); 32321308Sache if (sysctl(mib, 2, NULL, NULL, &wall_clock, len) == -1) { 32421308Sache syslog(LOG_ERR, "sysctl(put_wallclock): %m"); 32521308Sache return 1; 32621308Sache } 32721308Sache 32821308Sache if (need_restore) { 32921308Sache need_restore = False; 33021308Sache mib[0] = CTL_MACHDEP; 33121308Sache mib[1] = CPU_DISRTCSET; 33221308Sache disrtcset = 0; 33321308Sache len = sizeof(disrtcset); 33421308Sache if (sysctl(mib, 2, NULL, NULL, &disrtcset, len) == -1) { 33521308Sache syslog(LOG_ERR, "sysctl(restore_disrtcset): %m"); 33621308Sache return 1; 33721308Sache } 33821308Sache } 33921308Sache 34021308Sache/****** End of critical section ******/ 34121308Sache 34221308Sache if (init && wall_clock) { 34321308Sache sleep_mode = False; 34421308Sache /* wait for signals and acts like -a */ 34521308Sache (void) sigsuspend(&emask); 34621308Sache goto again; 34721308Sache } 34821308Sache 34921308Sache return 0; 35021308Sache} 35121308Sache