summaryrefslogtreecommitdiffstats
path: root/sbin/adjkerntz/adjkerntz.c
diff options
context:
space:
mode:
Diffstat (limited to 'sbin/adjkerntz/adjkerntz.c')
-rw-r--r--sbin/adjkerntz/adjkerntz.c350
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;
+}
OpenPOWER on IntegriCloud