Creating Rocky Linux 9.2 for 32bit i686/i586

[Screenshot of Rocky Linux 9.2 desktop on Pentium 120MHz]

This page is an attempt to create a Rocky Linux 9 for 32bit i686/i586.

Warning: This doesn't work on MMX-only processors. Processors with MMX but no SSE will execute some SSE instructions as MMX instruction, which lacks #UD exception for emulating it.
Note: This is just a technical challenge. It is insanely impractical to deploy Rocky Linux/AlmaLinux/CentOS Stream 9 on sub-GHz CPUs in any way.

It works fairly well on Pentium M 1.8GHz. KDE 5 is somewhat sluggish, but works.

Some figures deployed on Pentium 120MHz:


$Keywords: Rocky Linux 9, AlmaLinux 9, CentOS Linux 9, 32bit, el9.i686, el9.i586, installation media respin, anaconda, Pentium, CMOV emulation, SSE emulation, SSE2 emulation, QtWebEngine without SSE2 $

$Id$ (2023/05)


Does this work on my machine?

Your CPU has capability of
cmov mmx sse sse2 works? Example CPUs
yesplain Pentium
mmx noPentium MMX, K6-2, WinChip
cmovmmx noPentium II, Cyrix MII
cmovmmxsse noPentium III, Athlon XP
cmovmmxssesse2 yesPentium M, Pentium 4

The reason for all this fuss is that not all binaries are re-compiled. RHEL provided userland .i686 RPMs includes CMOV and SSE2 instructions which needs emulation, but MMX/SSE only processors cannot emulate them properly.

But worth a try on your machine; it seems that when compiling enough packages for installer to work, seems to make SSE/SSE2 codepath away at least for anaconda installer to complete.

Memory Requirement

You need enough memory to fit the unexpanded (70MB) and expanded (~220MB) initrd of anaconda installer, plus kernel itself (20MB uncompressed), plus the work memory. 512MB was not enough.

anaconda installer is written in Python, which inherently hogs a lot of memory.


Things you need

Compiled kernel's /lib/modules/5.14.0-*/bls.conf "title" label will be taken from the work machine's /etc/os-release , so using the same distribution for work machine and target is recommended.


Preparing i686 compilation environment

Compiling for .i686.rpm packages should be done in setarch i686'ed chroot environment. Many packages just doesn't cross-compile properly in x86_64 environment.

On the work machine, make SELinux permissive. This is a lorax(1) requirement. You need a reboot for this. Build of sed package fails if SELinux is totally disabled.

Bind-mounting a chroot filesystems

Suppose you are prepring a chroot environment in /chroot/i686/ of the work machine. After sudo mkdir -p /chroot/i686/, add the following entries in /etc/fstab :

# /chroot/i686
/proc		/chroot/i686/proc	none auto,bind 0 0
/dev		/chroot/i686/dev	none auto,bind 0 0
/dev/pts	/chroot/i686/dev/pts	none auto,bind 0 0
tmpfs		/chroot/i686/dev/shm	tmpfs auto,nosuid,nodev 0 0
sysfs		/chroot/i686/sys	sysfs auto,rw,nosuid,nodev,noexec 0 0
/sys/fs/cgroup	/chroot/i686/sys/fs/cgroup none auto,bind 0 0
## to let systemd build test see a /sys/fs/cgroup/systemd fs_type
/sys/fs/cgroup/systemd	/chroot/i686/sys/fs/cgroup/systemd none auto,bind 0 0
## to let systemd build test see /run/systemd/session/<#>
/run		/chroot/i686/run	none auto,bind 0 0
## to let sed build test see selinux
selinuxfs	/chroot/i686/sys/fs/selinux	selinuxfs auto 0 0

/home		/chroot/i686/home	none auto,bind 0 0

Do:

base$ sudo mkdir -p /chroot/i686/{proc,dev,sys,run} /chroot/i686/home

Then, manually mount the entries above, or just reboot to mount them all.

Bootstrapping 32bit development environment in the chroot

The goal is to prepare enough 32bit packages into /chroot/i686/ to run rpmbuild(8).

Drop-in ix86-r9.repo as /chroot/i686/etc/yum.repos.d/ix86-r9.repo .

base$ sudo mkdir -p /chroot/i686/etc/yum.repos.d/
base$ sudo cp ix86-r9.repo /chroot/i686/etc/yum.repos.d/ix86-r9.repo
base$ sudo cp -p /etc/resolv.conf /chroot/i686/etc/
base$ sudo cp -p /etc/passwd /etc/shadow /etc/group /chroot/i686/etc/

Install rpm-build package and mass dependencies using dnf --installroot . Use precompiled packages provided by this site for bootstrapping.

base$ sudo setarch i686 \
    dnf --installroot=/chroot/i686 --releasever=9 \
    --disablerepo=\* \
    --enablerepo=ix86repo\* \
    --verbose install rpm-build sudo

Pull in other packages needed for rpmbuild:

base$ sudo setarch i386 \
    dnf --installroot=/chroot/i686 --releasever=9 \
    --disablerepo=\* \
    --enablerepo=ix86repo\* \
    --verbose install dnf gcc make vi

chrooting to the i686 environment

With bash(1) installed in the chroot, it should be able to chroot inside. Check uname -a to see if it is an i686 environment.

base$ uname -a
Linux rocky9.five.ten 5.14.0-168.el9.x86_64 #1 SMP PREEMPT_DYNAMIC Fri Sep 23 11:43:25 UTC 2022 x86_64 x86_64 x86_64 GNU/Linux
base$ sudo setarch i686 chroot /chroot/i686 su - $LOGNAME
i686$ uname -a
Linux rocky9.five.ten 5.14.0-168.el9.x86_64 #1 SMP PREEMPT_DYNAMIC Fri Sep 23 11:43:25 UTC 2022 i686 i686 i386 GNU/Linux
i686$
This is tedious, so you want to add the following to ~/.bashrc in the work machine's base x86_64 system:
if [ `uname -m` != i686 ]; then
	alias i686env="sudo setarch i686 chroot /chroot/i686 su - $LOGNAME"
else
	PS1="[\u@i686 \W]\$ "
fi


Compiling a Package

Now, you would compile the individual packages.

To compile a package,

mkdir a dedicated directory

Since you are mass-building various packages, directly building under ~/rpmbuild/ is not recommended. The example below uses ~/r9builds/coreutils-r9/ for coreutils package build.

base$ sudo setarch i686 chroot /chroot/i686 su - $LOGNAME
user@i686$ cd
user@i686$ mkdir -p r9builds/coreutils-r9
user@i686$ cd r9builds/coreutils-r9
user@i686$ pwd
/home/user/r9builds/coreutils-r9

Prepare the directory for rpmbuild

You would like to run a following mkrpmdir shellscript inside the directory:

#!/bin/sh

### mkdir
for d in SPECS SOURCES; do mkdir -p $d; done

### ./rpmbin
cat > ./rpmbin << 'EOF'
#!/bin/sh

D=${0%/*}
## bash builtin "pwd" will return virtual path when
## symlink involves in the path. Avoid it.
## Otherwise /usr/lib/rpm/debugedit will be screwed.
test x"$D" = x"." && D="`/bin/pwd`"

#--target=i586
exec ${0##*/} -D "_topdir $D" "$@"

EOF
chmod +x ./rpmbin

### symlink ./rpmbuild, ./rpm -> rpmbin
for i in rpmbuild rpm; do rm -f ./$i; ln -s rpmbin ./$i; done

### Makefile
if [ ! -e ./Makefile ]; then
cat > ./Makefile << 'EOF'
PKG=$(shell ls SPECS/*.spec | sed -ne 's:SPECS/\(.*\)\.spec$$:\1:p')
DIST=.el9
binary:
	CPPFLAGS="-D_TIME_BITS=64 -D_FILE_OFFSET_BITS=64" \
	./rpmbuild --target=i586 -v -bb \
	-D 'dist $(DIST)' \
	SPECS/$(PKG).spec 2>&1 | \
	while IFS="" read line; do echo `date '+%Y-%m-%d %T'` "$$line"; done | \
	tee log

src:
	./rpmbuild --target=i586 -v -bs \
	-D 'dist $(DIST)' \
	SPECS/$(PKG).spec
EOF
fi
This script prepares ./rpm, ./rpmbuild and sample ./Makefile to install/compile the package.

Download a source rpm

Source RPMs are available under repo/Source/, or if not, under Rocky Linux mirror. For example, source RPM for coreutils package is at https://download.rockylinux.org/pub/rocky/9.2/BaseOS/source/tree/Packages/c/coreutils-8.32-34.el9.src.rpm . Download it:

user@i686$ curl -R -O https://download.rockylinux.org/pub/rocky/9.2/BaseOS/source/tree/Packages/c/coreutils-8.32-34.el9.src.rpm
You would like to take note what directory the package goes eventually:
user@i686$ echo BaseOS > reponame

Expand the source rpm in the directory

"Install" the source rpm in the current directory. You do not need a root privilege for source installation and compile.

user@i686$ ./rpm -ivh coreutils-8.32-34.el9.src.rpm
Note that you use ./rpm . It installs the source RPM in current directory and populates ./SPECS/ and ./SOURCE/ .

Invoke rpmbuild to build the binary RPM

You will invoke ./rpmbuild multiple times until it compiles, so ./Makefile template is provided by above mkrpmdir script.

PKG=$(shell ls SPECS/*.spec | sed -ne 's:SPECS/\(.*\)\.spec$$:\1:p')
DIST=.el9
binary:
	CPPFLAGS="-D_TIME_BITS=64 -D_FILE_OFFSET_BITS=64" \
	./rpmbuild --target=i586 -v -bb \
	-D 'dist $(DIST)' \
	SPECS/$(PKG).spec 2>&1 | \
	while IFS="" read line; do echo `date '+%Y-%m-%d %T'` "$$line"; done | \
	tee log

src:
	./rpmbuild --target=i586 -v -bs \
	-D 'dist $(DIST)' \
	SPECS/$(PKG).spec
Change the emphasized part according to the package.

Invoke rpmbuild to build the binary RPM

You have made a Makefile, so invoke

[kabe@i686 coreutils-r9]$ make
CPPFLAGS="-D_TIME_BITS=64 -D_FILE_OFFSET_BITS=64" \
./rpmbuild --target=i586 -v -bb \
-D 'dist .el9' \
SPECS/coreutils.spec 2>&1 | \
while IFS="" read line; do echo `date '+%Y-%m-%d %T'` "$line"; done | \
tee log
2023-03-29 11:10:18 Building target platforms: i586
2023-03-29 11:10:18 Building for target i586
2023-03-29 11:10:18 setting SOURCE_DATE_EPOCH=1646092800
2023-03-29 11:10:18 error: Failed build dependencies:
2023-03-29 11:10:18     attr is needed by coreutils-8.32-32.el9.i586
2023-03-29 11:10:18     autoconf is needed by coreutils-8.32-32.el9.i586
2023-03-29 11:10:18     automake is needed by coreutils-8.32-32.el9.i586
2023-03-29 11:10:18     gettext-devel is needed by coreutils-8.32-32.el9.i586
2023-03-29 11:10:18     glibc-langpack-en is needed by coreutils-8.32-32.el9.i586
2023-03-29 11:10:18     glibc-langpack-fr is needed by coreutils-8.32-32.el9.i586
2023-03-29 11:10:18     glibc-langpack-ko is needed by coreutils-8.32-32.el9.i586
2023-03-29 11:10:18     gmp-devel is needed by coreutils-8.32-32.el9.i586
2023-03-29 11:10:18     hostname is needed by coreutils-8.32-32.el9.i586
2023-03-29 11:10:18     libacl-devel is needed by coreutils-8.32-32.el9.i586
2023-03-29 11:10:18     libattr-devel is needed by coreutils-8.32-32.el9.i586
2023-03-29 11:10:18     libcap-devel is needed by coreutils-8.32-32.el9.i586
2023-03-29 11:10:18     libselinux-devel is needed by coreutils-8.32-32.el9.i586
2023-03-29 11:10:18     libselinux-utils is needed by coreutils-8.32-32.el9.i586
2023-03-29 11:10:18     openssl-devel is needed by coreutils-8.32-32.el9.i586
2023-03-29 11:10:18     perl(FileHandle) is needed by coreutils-8.32-32.el9.i586
2023-03-29 11:10:18     strace is needed by coreutils-8.32-32.el9.i586
2023-03-29 11:10:18     texinfo is needed by coreutils-8.32-32.el9.i586
Log will be logged in ./log file. You will normally have dependencies like above to resolve for building packages. Recursively build and install them, or install from this site:
i686# dnf --disablerepo=\* --enablerepo=ix86repo-\* install autoconf
Repeat recursive build, install and make until binary RPMs are built.


Packages modified for 32bit

Kernel

Although the not hardest to compile for 32bit, kernel package has the most patches.

RPM toolset

These are patched to let .i686 packages install on i586 host. (Usually rpm refuses so)

anaconda

Patched for slower machine deployment.

D-Bus and systemd patched to allow longer timeout

Eliminate hardcoded cmov, sse2

Refer Source directory for other modified packages.

Note: QtWebEngine (Chromium) is about 20 times heavier than KHTML even after eliminating SSE2. Konqueror provided on this page is patched to use KHTML by default. (EPEL Konqueror defaults to QtWebEngine)

Opcode emulation kernel

The kernel has CMOV, NOPL, FCOMI, FCMOVcc opcode emulation, and some SSE2 emulation enough to run compiler-generated (-mfpmath=sse) SSE2 instructions. This will let .i686 binaries run on i586 CPUs, which lacks cmov and sse2 capability.

Note: opcode emulation is very slow in nature. Recompile for .i586 whenever possible.

Counts of emulated opcodes will be available under /proc/emulated_ops:

$ cat /proc/emulated_ops
cmov:   3750123
nopl:   0
fcomi:  0
fucomi: 0
fcmov:  0
sse:    32682918
sse2:   1489899

GNOME3 is heavy; use KDE5

On RHEL 9, for desktop environment, only GNOME 3 is provided, but on slow machines I strongly recommend KDE. GNOME3 had become too heavyweight for sub-GHz, single-thread processor. KDE 5 is provided via EPEL, but since it provides only x86_64 binaries, every KDE 5 components had to be recompiled for 32bit.


Packages needed to be copied from Rocky Linux repository

Collect .i686 packages listed in rocky-packages.txt from your favorite Rocky Linux x86_64 repository or mirror. For downloading, dnf download --downloadonly --downloaddir=`pwd` package will download it in the current directory, but timestamp is not preserved. Recommend downloading by wget or curl -R -O .


Packages needed to be compiled

Compile source packages listed in compile-packages.txt .

These packages needs to be compiled because either

Use Source RPMs in Source directory if available. If there wasn't, use Source RPMs from Rocky Linux downloads or mirror.

Packages needed to be collected outside Rocky Linux

Following packages may not have corresponding source RPM in Rocky Linux mirror. You may have to pick them up from Rocky Linux Koji, Fedora 36 mirror or CentOS Stream buildsystem .

aspell-en gtest hidapi mm-common ncompress passt psutils python3-mallard-ducktype rapidjson spirv-headers umockdev unicode-emoji xorg-x11-font-utils

EPEL packages for KDE 5

For KDE 5 desktop, additional EPEL source packages must be compiled.

Modifying Makefile for Y2038 compliance

Inspect the SPECS/*.spec file.

If %build begins with %configure

Just passing CPPFLAGS="-D_TIME_BITS=64 -D_FILE_OFFSET_BITS=64" as environment value to rpmbuild is often enough.

If %build begins with %{qmake_qt5}
Pass %define to ./rpmbuild:
./rpmbuild --target=i586 -v -bb \
-D "_qt5_cflags -D_TIME_BITS=64 -D_FILE_OFFSET_BITS=64 -fcf-protection=none" \
-D "_qt5_cxxflags -D_TIME_BITS=64 -D_FILE_OFFSET_BITS=64 -fcf-protection=none" \
-D 'dist $(DIST)' 
	

If %build begins with %cmake , %meson, or direct %make_build

You must fiddle *.spec with CFLAGS before invocation, as:

+%bcond_with use_time_bits64
 ...
 %build
+
+%if %{with use_time_bits64}
+export CFLAGS="${CFLAGS:-%{build_cflags}} -D_TIME_BITS=64 -D_FILE_OFFSET_BITS=64"
+export CXXFLAGS="${CXXFLAGS:-%{build_cxxflags}} -D_TIME_BITS=64 -D_FILE_OFFSET_BITS=64"
+%endif
+%ifarch i586
+export CFLAGS="${CFLAGS:-%{build_cflags}} -fcf-protection=none"
+export CXXFLAGS="${CXXFLAGS:-%{build_cxxflags}} -fcf-protection=none"
+%endif
+
 %meson
	
and invoke rpmbuild with --with use_time_bits64 option.

Other cases:
There are few other cases; differs on what environment variable the *.spec files touches with.

Collecting Compiled Packages into a Repository

You need to collect the downloaded/compiled RPMs and assemble them as a repository, which lorax(1) requires.

Preparing a Repository directory

Prepare another directory for a repository:

user@i686$ cd
user@i686$ mkdir ix86.repo
user@i686$ cd ix86.repo
user@i686$ mkdir -p BaseOS/Packages AppStream/Packages CRB/Packages devel/Packages epel/Packages

Collecting compiled RPMs

Then, collect the downloaded/compiled binary RPMs into the above directories. You don't have to strictly follow the location of the directory as published in Rocky/RHEL; if you are lazy, just cramming everything into BaseOS/Packages/ may work.

user@i686$ find ../r9builds/coreutils-r9/RPMS/ -name '*.rpm' | egrep -v -- '-debuginfo-|-debugsource-' | xargs ln -t BaseOS/Packages
You would like to take advantage of "reponame" file made earlier for automatic sieving.

Run createrepo_c

First, browse in the Rocky Linux x86_64 binary repository for comps.xml file. If your favorite mirror is located at https://download.rockylinux.org/pub/rocky/9.2/BaseOS/x86_64/os/ , the comps.xml file may be at https://download.rockylinux.org/pub/rocky/9.2/BaseOS/x86_64/os/repodata/af2de794-848d-43be-8637-230316b5d4c4-GROUPS.xml . Copy it as ./comps-BaseOS.x86_64.xml (for later use).

Then, invoke createrepo_c against the RPM collection directory:

user@i686$ mkdir -p ./BaseOS/repodata/
user@i686$ cp -p comps-BaseOS.x86_64.xml ./BaseOS/repodata/comps.xml
user@i686$ createrepo_c -v --groupfile repodata/comps.xml ./BaseOS
Do the same thing against ./AppStream/ , ./CRB/ , ./devel/ and ./epel/ directory.

Add module information

Since the compiled RPMs are not "modularized", there are no module info in the repository and anaconda installation will fail. Install a repo2module program.

user@i686$ dnf --enablerepo=ix86\* modulemd-tools
Add module information to the repository:
user@i686$ cd
user@i686$ cd ix86.repo
user@i686$ repo2module --module-name=BaseOS --module-stream=stable ./BaseOS modules.yaml
## ./modules.yaml will be created. No need to edit for current purpose
user@i686$ modifyrepo_c --mdtype=modules modules.yaml BaseOS/repodata
Do the same thing against ./AppStream/ and ./CRB/ .


Assembling anaconda installer

After you had made the repository, it's the time to use lorax(1) to assemble the anaconda installer.

Install lorax package from official repository or media.

$ sudo dnf --disablerepo=\* --enablerepo=media\* install lorax

Create boot image with lorax

Prepare a separate directory for boot image assembling. Begin with a pristine directory to store the boot.iso file:

base$ cd
base$ mkdir r9lorax
base$ cd r9lorax

base$ rm -fr ./img9

Download add_template.tmpl file. You need this if the target machine is a slow machine.

Then, invoke the lorax with some options. Below assumes that assembled repository resides in ../ix86.repo/ .

base$ LANG=C sudo lorax -p "Rocky Linux" -v 9.2 -r 9.2 \
	-s `pwd`/../ix86.repo/BaseOS \
	-s `pwd`/../ix86.repo/AppStream \
	-s `pwd`/../ix86.repo/CRB \
	--installpkgs=systemd-devel \
	--add-template `pwd`/add_template.tmpl \
	--buildarch=i686 --nomacboot \
	`pwd`/img9
This takes a about 20 minutes on 3GHz-4thread machine. Logfiles are automatically created as ./lorax.log, ./pylorax.log, ./program.log .

As a result, ./img9/ will be populated with installer boot files, notably ./img9/images/boot.iso .

Scratch directories, owned by root, may be lying around in /var/tmp/lorax/ directory. You can safely delete them.

The created files are owned by root, so you would like to

base$ sudo chown -R $LOGNAME ./img9
base$      chmod -R +w       ./img9

The generated ./img9/images/boot.iso of about 842MB should boot as a network installer. You would like to try it out on target machine to see if the anaconda installer would work. (from RHEL 8.5, even the network installer does not fit on a CD.)

Discussion

lorax does not use the native files of the work machine to build the installer; it unpacks files from the *.rpm in the -s path directory. Thus the work host doesn't have to be i686; working on x86_64 should be okay.


Collect packages from the repository to a DVD tree

Copy (hardlink) over your repository contents to ./img9/ .

base$ sudo chown -R $LOGNAME ./img9
base$ rm -fr ./img9/Packages ./img9/SPackages ./img9/repodata
base$ rm -fr ./img9/{BaseOS,AppStream,CRB,devel,epel}
base$ cp -rpl ../ix86.repo/{BaseOS,AppStream,CRB,devel,epel} ./img9/

Optionally, if the target machine is slow, append the kernel boot line with inst.xtimeout=600 (wait for 600 seconds for Xorg to start; default 60 secs)

base$ sed -i -e 's/ quiet$/ inst.xtimeout=600&/' ./img9/isolinux/isolinux.cfg


Regenerate .treeinfo

The ./img9/.treeinfo generated by lorax doesn't mention BaseOS/, AppStream/ et al directives. You must recreate it.

You need to install python3-productmd package beforehand. If the work host is not Fedora, RHEL or CentOS, you have to patch /usr/lib/python3.9/site-packages/productmd/treeinfo.py .

Download productmd-treeinfo.py file, and invoke as:

base$ if [ ! -e ./img9/.treeinfo.lorax ]; then cp -p ./img9/.treeinfo ./img9/.treeinfo.lorax; fi
base$ python3 ./productmd-treeinfo.py ./img9/.treeinfo.lorax > ./img9/.treeinfo


Respin the media

Now you can respin the DVD media image.

base$ xorriso -o ./DVD1.iso \
	-b isolinux/isolinux.bin -c isolinux/boot.cat \
	-no-emul-boot -boot-load-size 4 -boot-info-table \
	-R -T -J \
	-v \
	-V "Rocky-Linux-9-2-i386" \
	-m upgrade.img -m boot.iso \
	-m 'texlive-*' \
	-m 'mingw*' -m ''gcc-toolset-12-*' \
	-m 'java-11-openjdk-src-*' \
	-m 'java-11-openjdk-*-devel-*' \
	-m 'java-11-openjdk-*-slowdebug-*' \
	-no-pad -iso-level 3 -D --hardlinks -joliet-long \
	./img9
base$ implantisomd5 ./DVD1.iso

The volume label (-V "Rocky-Linux-9-2-i386") should match with kernel option in ./img9/isolinux/isolinux.cfg, inst.stage2=hd:LABEL=Rocky-Linux-9-2-i386 .
Excluding boot.iso (~800MB) and texlive-* (~480MB) is for lowering size of the final DVD1.iso .

This will create a DVD1.iso image around 4.6GB. Burn the DVD1.iso and try it out on your target machine.


Troubleshooting


kabe.sra-tohoku.co.jp