adjkerntz.c revision 8871
1193323Sed/* 2193323Sed * Copyright (C) 1993, 1994, 1995 by Andrey A. Chernov, Moscow, Russia. 3193323Sed * All rights reserved. 4193323Sed * 5193323Sed * Redistribution and use in source and binary forms, with or without 6193323Sed * modification, are permitted provided that the following conditions 7193323Sed * are met: 8193323Sed * 1. Redistributions of source code must retain the above copyright 9193323Sed * notice, this list of conditions and the following disclaimer. 10193323Sed * 2. Redistributions in binary form must reproduce the above copyright 11193323Sed * notice, this list of conditions and the following disclaimer in the 12193323Sed * documentation and/or other materials provided with the distribution. 13193323Sed * 14193323Sed * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND 15193323Sed * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 16193323Sed * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 17261991Sdim * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE 18193323Sed * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 19276479Sdim * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 20276479Sdim * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 21193323Sed * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 22193323Sed * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 23193323Sed * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 24276479Sdim * SUCH DAMAGE. 25210299Sed */ 26193323Sed 27210299Sed#ifndef lint 28193323Sedchar copyright[] = 29193323Sed"@(#)Copyright (C) 1993, 1994, 1995 by Andrey A. Chernov, Moscow, Russia.\n\ 30193323Sed All rights reserved.\n"; 31276479Sdim#endif /* not lint */ 32249423Sdim 33249423Sdim/* 34276479Sdim * Andrey A. Chernov <ache@astral.msk.su> Dec 20 1993 35249423Sdim * 36249423Sdim * Fix kernel time value if machine run wall CMOS clock 37249423Sdim * (and /etc/wall_cmos_clock file present) 38249423Sdim * using zoneinfo rules or direct TZ environment variable set. 39249423Sdim * Use Joerg Wunsch idea for seconds accurate offset calculation 40288943Sdim * with Garrett Wollman and Bruce Evans fixes. 41288943Sdim * 42198090Srdivacky */ 43193323Sed#include <stdio.h> 44193323Sed#include <signal.h> 45193323Sed#include <stdlib.h> 46193323Sed#include <unistd.h> 47218893Sdim#include <syslog.h> 48276479Sdim#include <sys/stat.h> 49193323Sed#include <sys/time.h> 50193323Sed#include <sys/param.h> 51193323Sed#include <machine/cpu.h> 52193323Sed#include <sys/sysctl.h> 53218893Sdim 54193323Sed#include "pathnames.h" 55193323Sed 56193323Sed/*#define DEBUG*/ 57193323Sed#define REPORT_PERIOD (30*60) 58193323Sed 59193323Sedvoid fake() {} 60193323Sed 61239462Sdimint main(argc, argv) 62239462Sdim int argc; 63288943Sdim char **argv; 64288943Sdim{ 65234353Sdim struct tm local, utc; 66234353Sdim struct timeval tv, *stv; 67193323Sed struct timezone tz, *stz; 68193323Sed int kern_offset; 69280031Sdim size_t len; 70193323Sed int mib[2]; 71251662Sdim /* Avoid time_t here, can be unsigned long or worse */ 72218893Sdim long offset, utcsec, localsec, diff; 73218893Sdim time_t initial_sec, final_sec; 74218893Sdim int ch, init = -1; 75276479Sdim int initial_isdst = -1, final_isdst, looping; 76276479Sdim int disrtcset, need_restore = 0; 77218893Sdim sigset_t mask, emask; 78280031Sdim 79280031Sdim while ((ch = getopt(argc, argv, "ai")) != EOF) 80280031Sdim switch((char)ch) { 81249423Sdim case 'i': /* initial call, save offset */ 82193323Sed if (init != -1) 83193323Sed goto usage; 84193323Sed init = 1; 85288943Sdim break; 86218893Sdim case 'a': /* adjustment call, use saved offset */ 87288943Sdim if (init != -1) 88288943Sdim goto usage; 89193323Sed init = 0; 90288943Sdim break; 91276479Sdim default: 92276479Sdim usage: 93288943Sdim fprintf(stderr, "Usage:\n\ 94276479Sdim\tadjkerntz -i\t(initial call from /etc/rc)\n\ 95193323Sed\tadjkerntz -a\t(adjustment call from crontab)\n"); 96198090Srdivacky return 2; 97193323Sed } 98218893Sdim if (init == -1) 99207618Srdivacky goto usage; 100207618Srdivacky 101193323Sed if (access(_PATH_CLOCK, F_OK)) 102207618Srdivacky return 0; 103218893Sdim 104207618Srdivacky sigemptyset(&mask); 105193323Sed sigemptyset(&emask); 106210299Sed sigaddset(&mask, SIGTERM); 107210299Sed 108207618Srdivacky openlog("adjkerntz", LOG_PID|LOG_PERROR, LOG_DAEMON); 109193323Sed 110207618Srdivacky (void) signal(SIGHUP, SIG_IGN); 111193323Sed 112207618Srdivacky if (init && daemon(0, 1)) { 113193323Sed syslog(LOG_ERR, "daemon: %m"); 114207618Srdivacky return 1; 115193323Sed } 116251662Sdim 117251662Sdimagain: 118276479Sdim 119276479Sdim (void) sigprocmask(SIG_BLOCK, &mask, NULL); 120276479Sdim (void) signal(SIGTERM, fake); 121276479Sdim 122276479Sdim diff = 0; 123276479Sdim stv = NULL; 124207618Srdivacky stz = NULL; 125193323Sed looping = 0; 126207618Srdivacky 127218893Sdim mib[0] = CTL_MACHDEP; 128288943Sdim mib[1] = CPU_ADJKERNTZ; 129288943Sdim len = sizeof(kern_offset); 130288943Sdim if (sysctl(mib, 2, &kern_offset, &len, NULL, 0) == -1) { 131207618Srdivacky syslog(LOG_ERR, "sysctl(get_offset): %m"); 132207618Srdivacky return 1; 133288943Sdim } 134193323Sed 135193323Sed/****** Critical section, do all things as fast as possible ******/ 136288943Sdim 137193323Sed /* get local CMOS clock and possible kernel offset */ 138193323Sed if (gettimeofday(&tv, &tz)) { 139193323Sed syslog(LOG_ERR, "gettimeofday: %m"); 140193323Sed return 1; 141193323Sed } 142261991Sdim 143261991Sdim /* get the actual local timezone difference */ 144280031Sdim initial_sec = tv.tv_sec; 145280031Sdim 146280031Sdimrecalculate: 147280031Sdim local = *localtime(&initial_sec); 148193323Sed if (diff == 0) 149280031Sdim initial_isdst = local.tm_isdst; 150193323Sed utc = *gmtime(&initial_sec); 151280031Sdim local.tm_isdst = utc.tm_isdst = initial_isdst; 152221345Sdim 153193323Sed /* calculate local CMOS diff from GMT */ 154193323Sed 155198090Srdivacky utcsec = mktime(&utc); 156193323Sed localsec = mktime(&local); 157193323Sed if (utcsec == -1 || localsec == -1) { 158193323Sed /* 159193323Sed * XXX user can only control local time, and it is 160226633Sdim * unacceptable to fail here for init. 2:30 am in the 161221345Sdim * middle of the nonexistent hour means 3:30 am. 162223017Sdim */ 163223017Sdim syslog(LOG_WARNING, 164221345Sdim "Nonexistent local time -- will retry after %d secs", REPORT_PERIOD); 165221345Sdim (void) signal(SIGTERM, SIG_DFL); 166276479Sdim (void) sigprocmask(SIG_UNBLOCK, &mask, NULL); 167276479Sdim (void) sleep(REPORT_PERIOD); 168276479Sdim goto again; 169288943Sdim } 170288943Sdim offset = utcsec - localsec; 171288943Sdim#ifdef DEBUG 172193323Sed fprintf(stderr, "Initial offset: %ld secs\n", offset); 173218893Sdim#endif 174193323Sed 175193323Sed /* correct the kerneltime for this diffs */ 176276479Sdim /* subtract kernel offset, if present, old offset too */ 177276479Sdim 178276479Sdim diff = offset - tz.tz_minuteswest * 60 - kern_offset; 179276479Sdim 180276479Sdim if (diff != 0) { 181276479Sdim#ifdef DEBUG 182276479Sdim fprintf(stderr, "Initial diff: %ld secs\n", diff); 183276479Sdim#endif 184276479Sdim /* Yet one step for final time */ 185276479Sdim 186276479Sdim final_sec = initial_sec + diff; 187276479Sdim 188276479Sdim /* get the actual local timezone difference */ 189276479Sdim local = *localtime(&final_sec); 190276479Sdim final_isdst = diff < 0 ? initial_isdst : local.tm_isdst; 191276479Sdim if (diff > 0 && initial_isdst != final_isdst) { 192276479Sdim if (looping) 193276479Sdim goto bad_final; 194276479Sdim looping++; 195276479Sdim initial_isdst = final_isdst; 196276479Sdim goto recalculate; 197276479Sdim } 198276479Sdim utc = *gmtime(&final_sec); 199276479Sdim local.tm_isdst = utc.tm_isdst = final_isdst; 200276479Sdim 201276479Sdim utcsec = mktime(&utc); 202276479Sdim localsec = mktime(&local); 203276479Sdim if (utcsec == -1 || localsec == -1) { 204276479Sdim bad_final: 205276479Sdim /* 206276479Sdim * XXX as above. The user has even less control, 207276479Sdim * but perhaps we never get here. 208276479Sdim */ 209276479Sdim syslog(LOG_WARNING, 210276479Sdim "Nonexistent (final) local time -- will retry after %d secs", REPORT_PERIOD); 211276479Sdim (void) signal(SIGTERM, SIG_DFL); 212276479Sdim (void) sigprocmask(SIG_UNBLOCK, &mask, NULL); 213276479Sdim (void) sleep(REPORT_PERIOD); 214276479Sdim goto again; 215276479Sdim } 216276479Sdim offset = utcsec - localsec; 217276479Sdim#ifdef DEBUG 218276479Sdim fprintf(stderr, "Final offset: %ld secs\n", offset); 219276479Sdim#endif 220276479Sdim 221276479Sdim /* correct the kerneltime for this diffs */ 222276479Sdim /* subtract kernel offset, if present, old offset too */ 223276479Sdim 224276479Sdim diff = offset - tz.tz_minuteswest * 60 - kern_offset; 225276479Sdim 226276479Sdim if (diff != 0) { 227276479Sdim#ifdef DEBUG 228276479Sdim fprintf(stderr, "Final diff: %ld secs\n", diff); 229276479Sdim#endif 230276479Sdim tv.tv_sec += diff; 231276479Sdim tv.tv_usec = 0; /* we are restarting here... */ 232276479Sdim stv = &tv; 233276479Sdim } 234276479Sdim } 235276479Sdim 236276479Sdim if (tz.tz_dsttime != 0 || tz.tz_minuteswest != 0) { 237276479Sdim tz.tz_dsttime = tz.tz_minuteswest = 0; /* zone info is garbage */ 238276479Sdim stz = &tz; 239276479Sdim } 240276479Sdim 241276479Sdim /* if init and something will be changed, don't touch RTC at all */ 242276479Sdim if (init && (stv != NULL || kern_offset != offset)) { 243276479Sdim mib[0] = CTL_MACHDEP; 244276479Sdim mib[1] = CPU_DISRTCSET; 245276479Sdim len = sizeof(disrtcset); 246276479Sdim if (sysctl(mib, 2, &disrtcset, &len, NULL, 0) == -1) { 247276479Sdim syslog(LOG_ERR, "sysctl(get_disrtcset): %m"); 248276479Sdim return 1; 249288943Sdim } 250288943Sdim if (disrtcset == 0) { 251288943Sdim disrtcset = 1; 252288943Sdim need_restore = 1; 253288943Sdim if (sysctl(mib, 2, NULL, NULL, &disrtcset, len) == -1) { 254288943Sdim syslog(LOG_ERR, "sysctl(set_disrtcset): %m"); 255288943Sdim return 1; 256288943Sdim } 257288943Sdim } 258288943Sdim } 259288943Sdim 260288943Sdim if (( (init && (stv != NULL || stz != NULL)) 261288943Sdim || (stz != NULL && stv == NULL) 262288943Sdim ) 263276479Sdim && settimeofday(stv, stz) 264276479Sdim ) { 265276479Sdim syslog(LOG_ERR, "settimeofday: %m"); 266276479Sdim return 1; 267276479Sdim } 268276479Sdim 269276479Sdim /* init: don't write RTC, !init: write RTC */ 270276479Sdim if (kern_offset != offset) { 271276479Sdim kern_offset = offset; 272276479Sdim mib[0] = CTL_MACHDEP; 273276479Sdim mib[1] = CPU_ADJKERNTZ; 274276479Sdim len = sizeof(kern_offset); 275276479Sdim if (sysctl(mib, 2, NULL, NULL, &kern_offset, len) == -1) { 276276479Sdim syslog(LOG_ERR, "sysctl(update_offset): %m"); 277276479Sdim return 1; 278276479Sdim } 279276479Sdim } 280276479Sdim 281276479Sdim if (need_restore) { 282276479Sdim need_restore = 0; 283276479Sdim mib[0] = CTL_MACHDEP; 284276479Sdim mib[1] = CPU_DISRTCSET; 285276479Sdim disrtcset = 0; 286276479Sdim len = sizeof(disrtcset); 287276479Sdim if (sysctl(mib, 2, NULL, NULL, &disrtcset, len) == -1) { 288276479Sdim syslog(LOG_ERR, "sysctl(restore_disrtcset): %m"); 289276479Sdim return 1; 290276479Sdim } 291276479Sdim } 292276479Sdim 293276479Sdim/****** End of critical section ******/ 294276479Sdim 295276479Sdim if (init) { 296276479Sdim init = 0; 297276479Sdim /* wait for signals and acts like -a */ 298276479Sdim (void) sigsuspend(&emask); 299276479Sdim goto again; 300276479Sdim } 301296417Sdim 302296417Sdim return 0; 303276479Sdim} 304276479Sdim