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!