Running Red Hat 8 from 2002 on Linux 5.7.0-rc3 from 2020

2020-05-04
Previous post: Pi Zero in a PSX Case: TV-Out
Next post: The WAD file format of WipEout Pure and Pulse (PSP)

I recently had the sudden urge to try Red Hat 8 on real hardware (as one has, apparently), and this 1 GHz Athlon thin client with Windows 98 was the perfect candidate for this project.

Please note: It's not a very good idea to run outdated software on networked computers. This is purely an archaeological exercise, and the machine I'm using it on doesn't have any important data and is not networked.

The Story So Far

How I got a working kernel and booted it from Windows 98 using loadlin and used a Busybox-based initrd to then create and format an ext2 partition and copy in a current Debian sid chroot (created using debootstrap and copied as tarball via a USB disk to the Windows partition), then use the Debian chroot userland and its ssh and tar (the rsync versions of RedHat 8 and current-day Debian sid were too far apart) is a story for another post.

Suffice to say, I now have Linux kernel 5.7.0-rc3 running on this machine, and the RedHat 8 installation copied into a directory I can chroot into.

Kernel

RedHat 8 shipped with the Linux kernel 2.4.18 in September 2002, the 5.7-rc3 kernel I tested here was released in April 2020. So far, the ABI of the Linux kernel seems super stable.

XFree86 on fbdev

Interestingly, by using the XFree86 (yes, 2002) fbdev driver and the sisfb driver in the kernel (vesafb also worked, but the machine in question has an onboard SiS graphics card, and maybe I'll try something with that in the future) and starting the X font server xfs inside the chroot, it was possible to start up a KDE session.

MP3 and GIF Patents

Most of the command-line and GUI programs worked fine, but some features that we take for granted today were patched out: xmms had MP3 support patched out, because the MP3 patents in the US expired between 2007 and 2017. Note that just downloading the xmms 1.2.7 and building/installing it restores MP3 playback functionality.

Also, with The GIMP you could not save GIF files, because the GIF patents hadn't expired yet in 2002. Did you know that PNG was more or less created because of the GIF patent situation?

Pet Tools

Building recent versions of Vim (8.2) and Python (3.8.2) with the old toolchain of RedHat 8 (it ships with glibc 2.2.93 and GCC 3.2, which obviously predates many language improvements; LLVM was only released in 2003 and clang in 2007) wasn't that difficult, I had to patch out some low level functions that just haven't been added to libc back then, but both actually compiled and started fine.

To put things into perspective, RH8 ships with Python 2.2.1 and Vim 6.1.

ps

But then I ran into a weird situation: ps crashed. Just typing ps made it segfault. Of course, ps needs to query stuff from the kernel, and this is mostly done via text files in procfs (the /proc filesystem). Likely Linux changed something there in the last 18 years.

Some more digging later, I identified the procps package (RH8 ships with version 2.0.7) as the source for the ps binary and libproc.so.2.0.7. Building the source and running ps under GDB (RH8 ships with version 5.2.1) shows that it fails to parse some text file content. The first bug was in the meminfo() function, which parses /proc/meminfo and returns a matrix of some values. How much space do we need to store the field numbers?

char fieldbuf[12];         /* bigger than any field name or size in kb */

Yeah, sure. That worked probably just fine on 2002-era kernels, but these days, there's fields such as ShmemHugePages which is clearly longer than this. A simple bump of the size to 64 (hello from 2020, this is surely bigger than any field name or size in kb, right?) fixed this particular issue for me.

The rest of the bugs were non-crasher bugs, but they caused a super useful "Internal error!" message to be printed (and of course, there's multiple places in the source with the same message, because having a more specific error message might be too helpful). Some printf debugging later it turns out that reading /proc/*/status these days contains text data longer than 512 bytes, so trying to parse the files fails somewhere when the internal buffer is truncated and some fields left unread.

In the end, I'm sure there are other bugs, but at least for basic usage, this patch fixes ps on RedHat 8 on recent kernels. Of course, one could try to just build a newer procps version or replace some utilities with Busybox, but this way it's more period-correct, and at least makes the ps utility provide period-correct bug-for-bug compatibility in case some tools or scripts depend on it.

The Patch

Download it here: procps-2.0.7-patched-kernel5.7.0-rc3.patch

diff -ru procps-2.0.7/proc/readproc.c procps-2.0.7-patched/proc/readproc.c
--- procps-2.0.7/proc/readproc.c   2000-07-10 21:55:39.000000000 +0200
+++ procps-2.0.7-patched/proc/readproc.c   2020-05-04 20:17:42.000000000 +0200
@@ -128,6 +128,13 @@
         P->vm_lib   = 0;
     }

+    /* FIXME Kernel 5.7.0-rc3 has the fields separated in lines:
+     * SigPnd: 0000000000000000
+     * ShdPnd: 0000000000000000
+     * SigBlk: 0000000080000000
+     * SigIgn: 0000000000000000
+     * SigCgt: 0000000000000000
+     **/
     tmp = strstr (S,"SigPnd:");
     if(tmp) sscanf (tmp,
 #ifdef SIGNAL_STRING
@@ -303,7 +310,7 @@
 proc_t* readproc(PROCTAB* PT, proc_t* rbuf) {
     static struct direct *ent;     /* dirent handle */
     static struct stat sb;     /* stat buffer */
-    static char path[32], sbuf[512];  /* bufs for stat,statm */
+    static char path[32], sbuf[4096]; /* bufs for stat,statm */
     int allocated = 0, matched = 0;    /* flags */
     proc_t *p = NULL;

@@ -403,7 +410,7 @@
 proc_t* ps_readproc(PROCTAB* PT, proc_t* rbuf) {
     static struct direct *ent;     /* dirent handle */
     static struct stat sb;     /* stat buffer */
-    static char path[32], sbuf[512];  /* bufs for stat,statm */
+    static char path[32], sbuf[4096]; /* bufs for stat,statm */
     int allocated = 0 /* , matched = 0 */ ;    /* flags */
     proc_t *p = NULL;

@@ -473,7 +480,7 @@


 void look_up_our_self(proc_t *p) {
-    static char path[32], sbuf[512];  /* bufs for stat,statm */
+    static char path[32], sbuf[4096]; /* bufs for stat,statm */
     sprintf(path, "/proc/%d", getpid());
     if (file2str(path, "stat", sbuf, sizeof sbuf) >= 0)
    stat2proc(sbuf, p);             /* parse /proc/#/stat */
diff -ru procps-2.0.7/proc/sysinfo.c procps-2.0.7-patched/proc/sysinfo.c
--- procps-2.0.7/proc/sysinfo.c    2000-07-10 21:36:13.000000000 +0200
+++ procps-2.0.7-patched/proc/sysinfo.c    2020-05-04 20:12:30.000000000 +0200
@@ -33,7 +33,7 @@
 #define MEMINFO_FILE "/proc/meminfo"
 static int meminfo_fd = -1;

-static char buf[1024];
+static char buf[1024*64];

 /* This macro opens filename only if necessary and seeks to 0 so
  * that successive calls to the functions are more efficient.
@@ -202,7 +202,7 @@
     static unsigned long long *row[MAX_ROW + 1];       /* row pointers */
     static unsigned long long num[MAX_ROW * MAX_COL];  /* number storage */
     char *p;
-    char fieldbuf[12];        /* bigger than any field name or size in kb */
+    char fieldbuf[64];        /* bigger than any field name or size in kb */
     int i, j, k, l;

     FILE_TO_BUF(MEMINFO_FILE,meminfo_fd);
@@ -226,7 +226,7 @@
     }
     else {
        while(*p) {
-          sscanf(p,"%11s%n",fieldbuf,&k);
+          sscanf(p,"%63s%n",fieldbuf,&k);
            if(!strcmp(fieldbuf,"MemTotal:")) {
                p+=k;
                sscanf(p," %Ld",&(row[meminfo_main][meminfo_total]));

Continue reading: The WAD file format of WipEout Pure and Pulse (PSP)
Thomas Perl · 2020-05-04