Subject: Making rtc_offset changable at run time
To: None <tech-kern@NetBSD.ORG>
From: Martin Husemann <martin@duskware.de>
List: tech-kern
Date: 04/18/2003 13:04:58
--xHFwDpU9dbj6ez1V
Content-Type: text/plain; charset=us-ascii
Content-Disposition: inline

I live in a strange environment. I have a i386 SMP machine that boots into
various windows versions and NetBSD. Unfortunately I can not set the timezone
settings in windows to UTC, and where I live we shift to and from daylight
saving time.

This means (I guess many of you already noticed) that my real time clock
runs at different values depending which OS I boot - or I have to manually
adjust the time on every boot, in every OS.

I've been doing the latter for some time (ntpdate=YES in NetBSD, and a script
running "net time /set /yes \\mysambaserver" in Windows) - but this is still
a bit unsatisfactory and suboptimal. I guess I'm not the only one in a similar
situation.

Now there are two solutions: either compile a new kernel with options 
RTC_OFFSET changed after DST got in effect and after it is gone, or use the
gdb based kernel binary patch method Bill Studenmund suggested to some online
BSD magazine recently (sorry, forgot the URL).

I could do either, but this still feels like a hack to me.

So I came up with an easy solution that works pretty well for me - completely
automagically and forever.

The basic idea is: make sysctl kern.rtc_offset writable while at securelevel 0
and adjust it to the proper value eraly in the booting process via an rc.d
script. You can have options RTC_OFFSET at 0 (as I do) or at your non-DST
offset if you prefer. The kernels idea of time will be wrong until the script
runs, but since it starts pretty early (especially before ntpdate and ntpd)
this does no major harm (AFAICT).

I have attached a script that does this (thanks to lukem@ and matt@) and a 
kernel patch.

Caveats:

This method will fail if you happen to boot into NetBSD in the time period
before or after the daylight saving gets in effect/ends by which the script
adjusts your rtc_offset (example: I use 0 in the kernel binary, we now run
at -120 here, so if I boot NetBSD within the two hours of the next DST change
[haven't thought about it too much, so I'm not sure if before or after is bad]
it will pick the one-hour off offset). I didn't bother with this since the
change happens late at night here and I don't boot too often at this time.
With a bit of effort the script could easily catch this event, by doing the
date/awk thing one more time after the sysctl and checking if the offset
differs. Maybe it should check if offset and kern.rtc_offset differs always,
and only call sysctl -w if they don't, and then just do this twice.

Obviously the default for the rc.d variable controlling this would be NO.

What do you think?

Martin

--xHFwDpU9dbj6ez1V
Content-Type: text/plain; charset=us-ascii
Content-Disposition: attachment; filename=windowsrtc

#! /bin/sh

# PROVIDE: windowsrtc
# REQUIRE: mountcritremote
# BEFORE:  ntpdate ntpd

. /etc/rc.subr

name="windowsrtc"
rcvar=$name
start_cmd="windowsrtc_start"
stop_cmd=":"

windowsrtc_start()
{
	rtcoff=$(date '+%z' | awk '{
		offset = int($1);
		if (offset < 0) {
			sign = -1;
			offset = -offset;
		} else {
			sign = 1;
		}
		minutes = offset % 100;
		hours = offset / 100;
		offset = sign * (hours * 60 + minutes);
		print offset;
	}')
	sysctl -w kern.rtc_offset=$((-1 * $rtcoff))
}

load_rc_config $name
run_rc_command "$1"

--xHFwDpU9dbj6ez1V
Content-Type: text/plain; charset=us-ascii
Content-Disposition: attachment; filename="sysctl.patch"

Index: kern_sysctl.c
===================================================================
RCS file: /cvsroot/src/sys/kern/kern_sysctl.c,v
retrieving revision 1.133
diff -c -u -r1.133 kern_sysctl.c
--- kern_sysctl.c	2003/03/27 18:34:18	1.133
+++ kern_sysctl.c	2003/04/18 10:35:49
@@ -337,6 +337,7 @@
 	int error, level, inthostid;
 	int old_autonicetime;
 	int old_vnodes;
+	int old_rtc_offset;
 	dev_t consdev;
 #if NRND > 0
 	int v;
@@ -487,7 +488,24 @@
 			autoniceval = PRIO_MAX;
 		return (error);
 	case KERN_RTC_OFFSET:
-		return (sysctl_rdint(oldp, oldlenp, newp, rtc_offset));
+		if (newp && securelevel > 0)
+			return (EPERM);
+		old_rtc_offset = rtc_offset;
+		error = sysctl_int(oldp, oldlenp, newp, newlen, &rtc_offset);
+		if (newp && !error && old_rtc_offset != rtc_offset) {
+			/* if we change the offset, adjust the time */
+			struct timeval tv, delta;
+			int s;
+
+			s = splclock();
+			tv = time;
+			splx(s);
+			delta.tv_sec = 60*(rtc_offset - old_rtc_offset);
+			delta.tv_usec = 0;
+			timeradd(&tv, &delta, &tv);
+			settime(&tv);
+		}
+		return (error);
 	case KERN_ROOT_DEVICE:
 		return (sysctl_rdstring(oldp, oldlenp, newp,
 		    root_device->dv_xname));

--xHFwDpU9dbj6ez1V--