Date and Time on UNIX V7

(This is a guest post by xorhash.)

Introduction

I’ve recently defeated one of my bigger inconveniences, broken DEL as backspace on the UNIX®† operating system, Seventh Edition (commonly known as UNIX V7 or just V7). However, I’ve had another pet peeve for a while: How much manual labor is involved in booting the system. Reader DOS found out that SIMH recently added support for SEND/EXPECT pairs to react to output from the simulator. Think of them like UUCP chat scripts, effectively. This can be used to automate the bootup procedure.

Yet DOS’s script skips over a part of the bootup procedure that can be fully automated with some additional tooling. Namely, setting the date/time to the current time as defined by the host system. As per boot(8), the operator is meant to set the date/time every time the system is brought up. This should be possible to automate, right?

Setting the Date Automatically

date(1) itself does not support setting years past 2000, so we need custom code in any case. SIMH, fortunately, also provides a way to get the current timestamp in the form of %UTIME%, which is interpreted in any argument to any command. I’ve thus written a utility called tsdate that takes a timestamp as argument and sets the current time to be that timestamp. I put the executable in /etc/tsdate, but there’s really no reason to do so other than not wanting to accidentally call it. Once tsdate is in place, changing DOS’s script slightly will already do the trick:

expect "\n\r# " send "/etc/tsdate %UTIME%\r\004"; c

This approach already has a minor amount of time drift ab initio, namely the difference between the actual time on the host system and the UNIX timestamp. In the worst case, this may be very close to 1. If for some reason you need higher accuracy than this, you’ll probably have a fairly hard time. I could imagine some kind of NTP-over-serial, but you’d need support for chat scripts to get past the authentication due to getty(8) spawning login(1).

The system is not suitable for usage past the year 2038. However, you can at least push it back until around 2100 by changing the internal representation of time to be an unsigned long instead of simply a long. time_t was not used systematically. Instead, everything assumed long as the type for times. This assumption is in a lot of places in userspace and even the man pages use long instead of time_t.

If you overflow tsdate’s timestamp, you’ll just get whatever happens when atol(3) overflows. There’s nothing in the standard library for parsing strings into unsigned long and the year 2038 is far enough away that I didn’t want to bother. stime(2) would presumably also need to be adjusted.

Year 2000 Compatibility in the System

V7 is surprisingly good at handling years past 2000. Most utilities can print years up to and including 2099 properly. Macros for nroff(1)/troff(1), however, are blissfully unaware that years past 1999 may exist. This causes man pages to be supposedly printed in the year 19118. The root cause for this is that the number register yr only holds the current year minus 1900. Patches to the -ms and -man macros are required. Similarly, refer(1) only considers years until 2000 to be actual years, though I did not bother patching that since it should only affect keyword matching.

Leap year handling is broken in two different places due to wrong leap year handling: at(1) and the undocumented dysize() function, used by date(1) as well as inside /usr/src/libc/gen/ctime.c for various purposes. This affects the standard library, so recompiling the entire userland is recommended. Because the calculation is just a naïve division by four, it actually works on the year 2000 itself. A year is a leap year, i.e. has February 29, if a year is:

  1. a multiple of 4, and
    1. not a multiple of 100, or
    2. a multiple of 400.

Leap seconds are also not accounted for, but that comes to nobody’s surprise. Leap seconds just add a second 60 to the usual 00-59, so they don’t hurt doing date calculations on timestamps unless precisely on a leap second. For my purposes, they can be ignored.

Note that I haven’t gone through the system with a fine-toothed comb. There can always be more subtle time/date issues remaining in the system. For my purposes, this works well enough. If other things crop up, you’re welcome to put them in the comments for future generations.

The Good Stuff

There’s really not much to it. Applying diff and recompilation of the affected parts is left as an exercise for the reader.

Files:

  1. tsdate.c
  2. tsdate.1m
  3. y2kpatches.diff

† UNIX is a trademark of The Open Group.

Thanks Apple!

iPod is disabled
iPod is disabled

I only have to wait 24,153,992 minutes to unlock my iPod!  That is just under 46 years.

So I haven’t used this in a few months, and the battery went flat, but with iOS 9 or whatever this is running it reset to 1970, and now won’t let me even try to unlock it.  Good thing the MacBook Air I sync’d it with is dead too.  I guess that’s 2012 for me.

I just don’t want to hard reset it, so I guess I’ll have to see if there is a root kit/hack to set the clock.

OpenBSD 5.5 released!

McFishy
McFishy

OpenBSD 5.5 was just released! And in case you don’t get the DeLorean reference, this release focuses on fixing the 2038 issue!

From the change list:

  • time_t is now 64 bits on all platforms.
    • From OpenBSD 5.5 onwards, OpenBSD is year 2038 ready and will run well beyond Tue Jan 19 03:14:07 2038 UTC.
    • The entire source tree (kernel, libraries, and userland programs) has been carefully and comprehensively audited to support 64-bit time_t.
    • Userland programs that were changed include arp(8)bgpd(8)calendar(8)cron(8)find(1)fsck_ffs(8)ifconfig(8)ksh(1)ld(1)ld.so(1)netstat(1)pfctl(8)ping(8)rtadvd(8)ssh(1)tar(1),tmux(1)top(1), and many others, including games!
    • Removed time_t from network, on-disk, and database formats.
    • Removed as many (time_t) casts as possible.
    • Format strings were converted to use %lld and (long long) casts.
    • Uses of timeval were converted to timespec where possible.
    • Parts of the system that could not use 64-bit time_t were converted to use unsigned 32-bit instead, so they are good till the year 2106.
    • Numerous ports throughout the ports tree received time_t fixes.

Wow, that’s pretty cool!

And of course for VMware users:

  • New vmx(4) driver for VMware VMXNET3 Virtual Interface Controller devices.
  • New vmwpvs(4) driver for VMware Paravirtual SCSI.
  • New vioscsi(4) driver for VirtIO SCSI adapters.
  • New viornd(4) driver for VirtIO random number devices.

In addition the new vxlan driver looks pretty interesting too!

As always get your copy from one of the many HTTP mirrors, and why not support the project with the purchase of a CD or poster?

free. functional and secure...
free. functional and secure…

NetBSD 6.0 released!

yay

  • SMP support for Xen domU kernels, initial suspend/resume support for Xen domU, PCI pass-through support for Xen3, and addition of the balloon driver.
  • Major rework of MIPS port adding support for SMP and 64-bit (O32, N32, N64 ABIs are supported) processors, DSP v2 ASE extension, various NetLogic/RMI processor models, Loongson family processors, and new SoC boards.
  • Improved SMP on PowerPC port and added support for Book E Freescale MPC85xx (e500 core) processors.
  • ARM has gained support for Cortex-A8 processors, various new SoCs, and initial support for Raspberry Pi. Full support for Raspberry Pi and major ARM improvements to come in a future NetBSD release.
  • time_t is now a 64-bit quantity on all NetBSD ports. This means that the NetBSD world no longer ends in 2037.

Interesting they addressed the 2038 issue… And more SMP support…

The 2038 ‘problem’.

Eventually you will start to hear about the 2038 problem regarding UNIX, and the C language. Looking at the 4.3 BSD source, the problem is pretty simple.

The time_t value holds the number of ‘ticks’ since new years, 1970. It’s a signed long, that can hold a maximum value of 2147483648. This will set you into the year 2038. There is some great detail on the site 2038bug.com . Naturally the easiest option here, is to simply change it from a signed long to an unsigned long. This will let us use a maximum value of 4294967296, which translates to the date Sun Feb 6 06:28:15 2106.

While this is not a “perfect” solution for some people, this works great on platforms that are not built with compilers that can understand 64bit “long longs”. Not to mention that constantly updating a pseudo long long could have negative implications in the kernel….

The bottom line is that eventually we need to move away from 32bit platforms, and with some programs that try to look 30+ years into the future, the time is NOW. But just because you are on a 64bit UNIX doesn’t mean the date isn’t being kept in a 32bit signed value for ‘compatibility’…

To test the idea, I’ve taken the 4.3 UWisc BSD from my project here, and gone about making some changes.

Starting with the file /usr/sys/h/types.h

myname# diff -r types.h x
47c47
< typedef long time_t;

> typedef unsigned long time_t;

Very simple, right? The kernel has it’s own version of this file, /usr/sys/h/types.h , and will require the same fix.

myname# diff -r types.h x
47c47
< typedef long time_t;

> typedef unsigned long time_t;

Now with that out of the way, the next thing to do is patch some of the functions in libc.

/usr/src/lib/libc/gen/time.c

myname# diff time.c /time.c
25c25
< long

> unsigned long

/usr/src/lib/libc/gen/ctime.c

myname# diff ctime.c /ctime.c
248c248
< long hms, day;

> unsigned long hms, day;

Ok, that’s the bulk of the changes. Really.

Rebuild the kernel & libc, and install them.

cd /usr/sys/GENERIC
make clean
make
cp vmunix /

cd /usr/src/lib/libc
make
cp libc.a /lib
cp libc_p.a /usr/lib
ranlib /lib/libc.a
ranlib /usr/lib/libc_p.a

Now with that out of the way, reboot the VM.

So far everything should behave normally. But it’s time for some fun!

First, let’s make sure we can use dates beyond the 2038 bug date. This program comes from the aforementioned 2038bug.com site. I’ve just added one header needed by 4.3 BSD to get the time_t structure called in correctly.

#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <time.h>
#include <sys/types.h>

int main (int argc, char **argv)
{
time_t t;
t = (time_t) 1000000000;
printf (“%d, %s”, (int) t, asctime (gmtime (&t)));
t = (time_t) (0x7FFFFFFF);
printf (“%d, %s”, (int) t, asctime (gmtime (&t)));
t++;
printf (“%u, %s”, (unsigned int) t, asctime (gmtime (&t)));
return 0;
}

Now when I run it I get this:

myname# gcc 2038.c;./2038
1000000000, Sun Sep 9 01:46:40 2001
2147483647, Tue Jan 19 03:14:07 2038
2147483648, Tue Jan 19 03:14:08 2038

Notice that it works!

Unfortunately the date command in BSD (well just any 32v derived UNIX) is set to handle two digit years. So what I’ve done is to ‘fix’ date to handle four digit years. So here is the diff:

myname# diff date.c date4year.c
94c94
< long time();

> unsigned long time();
160c160
< tv.tv_sec += (long)tz.tz_minuteswest*60;

> tv.tv_sec += (unsigned long)tz.tz_minuteswest*60;
167c167
<

> /*printf(“setting time….”);*/
228c228,229
< year = gp(L->tm_year);

> year = gp2(L->tm_year);
> /*printf(“gtime year %d\n”,year);*/
243c244,246
< year += 1900;

> /*year += 1900;
> no more 2 year dates!
> */
270a274,292
> gp2(dfault)
> {
> int c, d,e,f;
>
> if (*sp == 0)
> return (dfault);
> c = (*sp++) – ‘0’;
> d = (*sp ? (*sp++) – ‘0’ : 0);
> e = (*sp ? (*sp++) – ‘0’ : 0);
> f = (*sp ? (*sp++) – ‘0’ : 0);
> /*printf(“c %d d %d e %d f %d\n”,c,d,e,f);*/
>
> if (c < 0 || c > 9 || d < 0 || d > 9)
> return (-1);
> if (e < 0 || e > 9 || f < 0 || f > 9)
> return (-1);
> return ((f*1000)+(e*100)+(d*10)+c);
> }
>
293c315
< long waittime;

> unsigned long waittime;

Now I can set the time like this:

myname# ./date 201001021208
Sat Jan 2 12:08:00 PST 2010
myname# ./date
Sat Jan 2 12:08:01 PST 2010

And all is well.

So let’s take it to 2040!

myname# ./date 204001011433
Sun Jan 1 14:33:00 PST 2040
myname# ./date
Sun Jan 1 14:33:02 PST 2040

And there we have it!

The old date command would return Thu Nov 26 08:04:46 PST 1903, which is slightly wrong!

Naturally EVERY program that calls time/date stuff will have to be checked to be fully 2038 vetted, but you get the idea. The major offender in the date command is the idea of adding 1900 to the date, which is not needed when you specify the full year.

While this 2038 solution is simple, it still requires people to start to take action. It seems the industry is not moving yet, but it’ll certainly require source code access to make it quick & easy… But I’m sure like the Y2k issue, people will wait until 2036, and by then there’ll be good money in programs that can decompile various UNIX binaries, and change that signed long value… The ‘issue’ is the other fallout like the slight change I had to make to the 2038 test program….

Always remember to keep your source SAFE!

Oh, and happy new years!