diff options
Diffstat (limited to 'sbin/adjkerntz/adjkerntz.c')
-rw-r--r-- | sbin/adjkerntz/adjkerntz.c | 350 |
1 files changed, 350 insertions, 0 deletions
diff --git a/sbin/adjkerntz/adjkerntz.c b/sbin/adjkerntz/adjkerntz.c new file mode 100644 index 0000000..8cd3ff9 --- /dev/null +++ b/sbin/adjkerntz/adjkerntz.c @@ -0,0 +1,350 @@ +/* + * Copyright (C) 1993-1996 by Andrey A. Chernov, Moscow, Russia. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#ifndef lint +char copyright[] = +"@(#)Copyright (C) 1993-1996 by Andrey A. Chernov, Moscow, Russia.\n\ + All rights reserved.\n"; +#endif /* not lint */ + +/* + * Andrey A. Chernov <ache@astral.msk.su> Dec 20 1993 + * + * Fix kernel time value if machine run wall CMOS clock + * (and /etc/wall_cmos_clock file present) + * using zoneinfo rules or direct TZ environment variable set. + * Use Joerg Wunsch idea for seconds accurate offset calculation + * with Garrett Wollman and Bruce Evans fixes. + * + */ +#include <stdio.h> +#include <signal.h> +#include <stdlib.h> +#include <unistd.h> +#include <syslog.h> +#include <sys/stat.h> +#include <sys/time.h> +#include <sys/param.h> +#include <machine/cpu.h> +#include <sys/sysctl.h> + +#include "pathnames.h" + +/*#define DEBUG*/ + +#define True (1) +#define False (0) +#define Unknown (-1) + +#define REPORT_PERIOD (30*60) + +void fake() {} + +int main(argc, argv) + int argc; + char **argv; +{ + struct tm local, utc; + struct timeval tv, *stv; + struct timezone tz, *stz; + int kern_offset, wall_clock, disrtcset; + size_t len; + int mib[2]; + /* Avoid time_t here, can be unsigned long or worse */ + long offset, utcsec, localsec, diff; + time_t initial_sec, final_sec; + int ch; + int initial_isdst = -1, final_isdst; + int need_restore = False, sleep_mode = False, looping, + init = Unknown; + sigset_t mask, emask; + + while ((ch = getopt(argc, argv, "ais")) != EOF) + switch((char)ch) { + case 'i': /* initial call, save offset */ + if (init != Unknown) + goto usage; + init = True; + break; + case 'a': /* adjustment call, use saved offset */ + if (init != Unknown) + goto usage; + init = False; + break; + case 's': + sleep_mode = True; + break; + default: + usage: + fprintf(stderr, "Usage:\n\ +\tadjkerntz -i\t\t(initial call from /etc/rc)\n\ +\tadjkerntz -a [-s]\t(adjustment call, -s for sleep/retry mode)\n"); + return 2; + } + if (init == Unknown) + goto usage; + if (init) + sleep_mode = True; + + sigemptyset(&mask); + sigemptyset(&emask); + sigaddset(&mask, SIGTERM); + + openlog("adjkerntz", LOG_PID|LOG_PERROR, LOG_DAEMON); + + (void) signal(SIGHUP, SIG_IGN); + + if (init && daemon(0, 1)) { + syslog(LOG_ERR, "daemon: %m"); + return 1; + } + +again: + (void) sigprocmask(SIG_BLOCK, &mask, NULL); + (void) signal(SIGTERM, fake); + + diff = 0; + stv = NULL; + stz = NULL; + looping = False; + + wall_clock = (access(_PATH_CLOCK, F_OK) == 0); + if (init && !sleep_mode) { + init = False; + if (!wall_clock) + return 0; + } + + mib[0] = CTL_MACHDEP; + mib[1] = CPU_ADJKERNTZ; + len = sizeof(kern_offset); + if (sysctl(mib, 2, &kern_offset, &len, NULL, 0) == -1) { + syslog(LOG_ERR, "sysctl(get_offset): %m"); + return 1; + } + +/****** Critical section, do all things as fast as possible ******/ + + /* get local CMOS clock and possible kernel offset */ + if (gettimeofday(&tv, &tz)) { + syslog(LOG_ERR, "gettimeofday: %m"); + return 1; + } + + /* get the actual local timezone difference */ + initial_sec = tv.tv_sec; + +recalculate: + local = *localtime(&initial_sec); + if (diff == 0) + initial_isdst = local.tm_isdst; + utc = *gmtime(&initial_sec); + local.tm_isdst = utc.tm_isdst = initial_isdst; + + /* calculate local CMOS diff from GMT */ + + utcsec = mktime(&utc); + localsec = mktime(&local); + if (utcsec == -1 || localsec == -1) { + /* + * XXX user can only control local time, and it is + * unacceptable to fail here for init. 2:30 am in the + * middle of the nonexistent hour means 3:30 am. + */ + syslog(LOG_WARNING, + "Warning: nonexistent %s time.", + utcsec == -1 && localsec == -1 ? "UTC time and local" : + utcsec == -1 ? "UTC" : "local"); + if (!sleep_mode) { + syslog(LOG_WARNING, "Giving up."); + return 1; + } + syslog(LOG_WARNING, "Will retry after %d minutes.", + REPORT_PERIOD / 60); + (void) signal(SIGTERM, SIG_DFL); + (void) sigprocmask(SIG_UNBLOCK, &mask, NULL); + (void) sleep(REPORT_PERIOD); + goto again; + } + offset = utcsec - localsec; +#ifdef DEBUG + fprintf(stderr, "Initial offset: %ld secs\n", offset); +#endif + + /* correct the kerneltime for this diffs */ + /* subtract kernel offset, if present, old offset too */ + + diff = offset - tz.tz_minuteswest * 60 - kern_offset; + + if (diff != 0) { +#ifdef DEBUG + fprintf(stderr, "Initial diff: %ld secs\n", diff); +#endif + /* Yet one step for final time */ + + final_sec = initial_sec + diff; + + /* get the actual local timezone difference */ + local = *localtime(&final_sec); + final_isdst = diff < 0 ? initial_isdst : local.tm_isdst; + if (diff > 0 && initial_isdst != final_isdst) { + if (looping) + goto bad_final; + looping = True; + initial_isdst = final_isdst; + goto recalculate; + } + utc = *gmtime(&final_sec); + local.tm_isdst = utc.tm_isdst = final_isdst; + + utcsec = mktime(&utc); + localsec = mktime(&local); + if (utcsec == -1 || localsec == -1) { + bad_final: + /* + * XXX as above. The user has even less control, + * but perhaps we never get here. + */ + syslog(LOG_WARNING, + "Warning: nonexistent final %s time.", + utcsec == -1 && localsec == -1 ? "UTC time and local" : + utcsec == -1 ? "UTC" : "local"); + if (!sleep_mode) { + syslog(LOG_WARNING, "Giving up."); + return 1; + } + syslog(LOG_WARNING, "Will retry after %d minutes.", + REPORT_PERIOD / 60); + (void) signal(SIGTERM, SIG_DFL); + (void) sigprocmask(SIG_UNBLOCK, &mask, NULL); + (void) sleep(REPORT_PERIOD); + goto again; + } + offset = utcsec - localsec; +#ifdef DEBUG + fprintf(stderr, "Final offset: %ld secs\n", offset); +#endif + + /* correct the kerneltime for this diffs */ + /* subtract kernel offset, if present, old offset too */ + + diff = offset - tz.tz_minuteswest * 60 - kern_offset; + + if (diff != 0) { +#ifdef DEBUG + fprintf(stderr, "Final diff: %ld secs\n", diff); +#endif + tv.tv_sec += diff; + tv.tv_usec = 0; /* we are restarting here... */ + stv = &tv; + } + } + + if (tz.tz_dsttime != 0 || tz.tz_minuteswest != 0) { + tz.tz_dsttime = tz.tz_minuteswest = 0; /* zone info is garbage */ + stz = &tz; + } + if (!wall_clock && stz == NULL) + stv = NULL; + + /* if init or UTC clock and offset/date will be changed, */ + /* disable RTC modification for a while. */ + + if ( (init && stv != NULL) + || ((init || !wall_clock) && kern_offset != offset) + ) { + mib[0] = CTL_MACHDEP; + mib[1] = CPU_DISRTCSET; + len = sizeof(disrtcset); + if (sysctl(mib, 2, &disrtcset, &len, NULL, 0) == -1) { + syslog(LOG_ERR, "sysctl(get_disrtcset): %m"); + return 1; + } + if (disrtcset == 0) { + disrtcset = 1; + need_restore = True; + if (sysctl(mib, 2, NULL, NULL, &disrtcset, len) == -1) { + syslog(LOG_ERR, "sysctl(set_disrtcset): %m"); + return 1; + } + } + } + + if ( ( (init && (stv != NULL || stz != NULL)) + || (stz != NULL && stv == NULL) + ) + && settimeofday(stv, stz) + ) { + syslog(LOG_ERR, "settimeofday: %m"); + return 1; + } + + /* setting CPU_ADJKERNTZ have a side effect: resettodr(), which */ + /* can be disabled by CPU_DISRTCSET, so if init or UTC clock */ + /* -- don't write RTC, else write RTC. */ + + if (kern_offset != offset) { + kern_offset = offset; + mib[0] = CTL_MACHDEP; + mib[1] = CPU_ADJKERNTZ; + len = sizeof(kern_offset); + if (sysctl(mib, 2, NULL, NULL, &kern_offset, len) == -1) { + syslog(LOG_ERR, "sysctl(update_offset): %m"); + return 1; + } + } + + mib[0] = CTL_MACHDEP; + mib[1] = CPU_WALLCLOCK; + len = sizeof(wall_clock); + if (sysctl(mib, 2, NULL, NULL, &wall_clock, len) == -1) { + syslog(LOG_ERR, "sysctl(put_wallclock): %m"); + return 1; + } + + if (need_restore) { + need_restore = False; + mib[0] = CTL_MACHDEP; + mib[1] = CPU_DISRTCSET; + disrtcset = 0; + len = sizeof(disrtcset); + if (sysctl(mib, 2, NULL, NULL, &disrtcset, len) == -1) { + syslog(LOG_ERR, "sysctl(restore_disrtcset): %m"); + return 1; + } + } + +/****** End of critical section ******/ + + if (init && wall_clock) { + sleep_mode = False; + /* wait for signals and acts like -a */ + (void) sigsuspend(&emask); + goto again; + } + + return 0; +} |