Fathom-OS Build Series: Chapter 8 Part 1 - API and Core Libraries

Part of the Fathom-OS Project Log.


Chapter 8 is the core system build, the largest chapter in the book. It compiles the final, permanent versions of every package that makes up the running system, roughly 80 of them. Because of the size, I am splitting this chapter into four posts so each one stays readable instead of being a wall nobody finishes. This is Part 1: the API headers and the core libraries, from Man-pages through Pcre2. It includes the single most important package in the whole build, the final Glibc.

References:


Resuming Across Sessions: Remount and Re-enter

Chapter 7 ended with a snapshot taken outside the chroot with the virtual filesystems unmounted. So every new Chapter 8 session starts the same way: remount the kernel filesystems and re-enter the chroot. This is the cross-session procedure flagged in the Chapter 7 post, and it has to happen before any build work.

As root on the host, with $LFS confirmed as /lfs:

mount -v --bind /dev $LFS/dev
mount -vt devpts devpts -o gid=5,mode=0620 $LFS/dev/pts
mount -vt proc proc $LFS/proc
mount -vt sysfs sysfs $LFS/sys
mount -vt tmpfs tmpfs $LFS/run
if [ -h $LFS/dev/shm ]; then
  install -v -d -m 1777 $LFS$(realpath /dev/shm)
else
  mount -vt tmpfs -o nosuid,nodev tmpfs $LFS/dev/shm
fi

Then enter the chroot:

chroot "$LFS" /usr/bin/env -i \
    HOME=/root \
    TERM="$TERM" \
    PS1='(lfs chroot) \u:\w\$ ' \
    PATH=/usr/bin:/usr/sbin \
    MAKEFLAGS="-j$(nproc)" \
    TESTSUITEFLAGS="-j$(nproc)" \
    /bin/bash --login

The prompt comes back as (lfs chroot) root:/# directly this time, since passwd already exists from Chapter 7.


8.3 Man-pages

Over 2,400 man pages. Remove two crypt man pages first (Libxcrypt provides better ones later), then a direct install with no configure or compile.

rm -v man3/crypt*
make -R GIT=false prefix=/usr install

8.4 Iana-Etc

The simplest package in the book. Just copy two files providing network service and protocol data.

cp -v services protocols /etc

8.5 Glibc: The Big One

This is the final C library and the foundation everything else links against. It is the largest single package in the chapter at 12 SBU and 3.5 GB, and its test suite is marked critical, do not skip.

A note before the steps: the book has a long “If upgrading Glibc on a running LFS system” block. That is conditional and applies only to replacing Glibc on an already-built system. This is a fresh build, so that block does not apply. Every step that belongs to a clean first build gets run, all of it, including the full test suite.

Patch for FHS compliance, build in a dedicated directory, and set the correct sbin path:

patch -Np1 -i ../glibc-fhs-1.patch
mkdir -v build
cd build
echo "rootsbindir=/usr/sbin" > configparms

Configure:

../configure --prefix=/usr                   \
             --disable-werror                \
             --disable-nscd                  \
             libc_cv_slibdir=/usr/lib        \
             --enable-stack-protector=strong \
             --enable-kernel=5.4
make

The Critical Test Suite

make check

The result was clean: 6735 PASS, 2 FAIL. The two failures are both on the book’s known-safe list, and I confirmed each one by name rather than assuming.

The first, found with grep "Timed out" $(find -name \*.out), was malloc/tst-malloc-too-large-malloc-hugetlb2, a timeout. Timeouts are expected when running the suite with parallel jobs on a VM and the book lists this category as safe.

The second was io/tst-lchmod, which the book explicitly says is known to fail in the chroot. The failure proves it: the test compared a descriptor path and saw /sources/... versus /lfs/sources/..., which is purely the chroot path showing through, not a library defect.

The make check ends with a make “Error 1” and “Error 2”. That is just make reporting that some test failed, normal whenever any test fails, not a build problem.

Install and Configure

Prevent a harmless install warning, fix an outdated Makefile sanity check, install, then fix the ldd path:

touch /etc/ld.so.conf
sed '/test-installation/s@$(PERL)@echo not running@' -i ../Makefile
make install
sed '/RTLDLIST=/s@/usr@@g' -i /usr/bin/ldd

Install the locales (the book’s minimum set for test coverage), create /etc/nsswitch.conf, install the time zone data, set the local timezone, and configure the dynamic loader. The timezone for this build is America/New_York:

ln -sfv /usr/share/zoneinfo/America/New_York /etc/localtime

The nsswitch.conf and ld.so.conf files are created per the book, with the dynamic loader pointed at /usr/local/lib and /opt/lib plus an include directory at /etc/ld.so.conf.d.

A Leftover Caught During Cleanup

When removing the Glibc source, the sources directory also still held a util-linux-2.41.3 directory left over from Chapter 7, owned by the old lfs user ID rather than root, a sign its cleanup did not complete last session. Removed both:

cd /sources
rm -rf glibc-2.43
rm -rf util-linux-2.41.3

Worth checking the sources directory for stale extracted trees from prior sessions, not just the current package.


The Compression Libraries

A run of five compression libraries, mostly routine, each built, tested, and installed with its static library removed where the book calls for it.

8.6 Zlib

./configure --prefix=/usr
make
make check
make install
rm -fv /usr/lib/libz.a

Tests reported “zlib test OK”, “zlib 64-bit test OK”, “zlib shared test OK”.

8.7 Bzip2

A patch, two seds for symlinks and man page location, then a special shared-library build before the normal one:

patch -Np1 -i ../bzip2-1.0.8-install_docs-1.patch
sed -i 's@\(ln -s -f \)$(PREFIX)/bin/@\1@' Makefile
sed -i "s@(PREFIX)/man@(PREFIX)/share/man@g" Makefile
make -f Makefile-libbz2_so
make clean
make
make PREFIX=/usr install
cp -av libbz2.so.* /usr/lib
ln -sfv libbz2.so.1.0.8 /usr/lib/libbz2.so
ln -sfv libbz2.so.1.0.8 /usr/lib/libbz2.so.1
cp -v bzip2-shared /usr/bin/bzip2
for i in /usr/bin/{bzcat,bunzip2}; do ln -sfv bzip2 $i; done
rm -fv /usr/lib/libbz2.a

The mainGtU inlining warnings during the build are normal for bzip2 and harmless. This package is also where a real problem started, covered in the troubleshooting section below.

8.8 Xz

The final permanent Xz, replacing the temporary one from Chapter 6.

./configure --prefix=/usr --disable-static --docdir=/usr/share/doc/xz-5.8.2
make
make check
make install

8.9 Lz4

No configure, make variables direct. Note the test runs single-job per the book.

make BUILD_STATIC=no PREFIX=/usr
make -j1 check
make BUILD_STATIC=no PREFIX=/usr install

8.10 Zstd

make prefix=/usr
make check
make prefix=/usr install
rm -v /usr/lib/libzstd.a

The book warns that lowercase “failed” appears in the test output and is expected, only an uppercase “FAIL” is a real failure. The run ended with “All tests completed successfully”.


8.11 File and 8.12 Readline

File is the final permanent version, simpler than the Chapter 6 temporary build since the two-stage host trick is no longer needed:

./configure --prefix=/usr
make
make check
make install

Readline needs four seds before configure (two to avoid an ldconfig linking bug, one to strip rpath, one upstream fix to input.c) and a special make option to link against libncursesw. It has no test suite:

make SHLIB_LIBS="-lncursesw"
make install

The “you may need to run ldconfig” message at the end is informational, not an error.


8.13 Pcre2

The Perl Compatible Regular Expression libraries, built with Unicode, JIT, 16 and 32 bit support, and integration with zlib, bzip2, and readline:

./configure --prefix=/usr                       \
            --docdir=/usr/share/doc/pcre2-10.47 \
            --enable-unicode --enable-jit       \
            --enable-pcre2-16 --enable-pcre2-32 \
            --enable-pcre2grep-libz             \
            --enable-pcre2grep-libbz2           \
            --enable-pcre2test-libreadline      \
            --disable-static
make
make check
make install

Tests passed clean: TOTAL 4, PASS 4, FAIL 0.


Troubleshooting: When bzip2 Quietly Stopped Working

This part had the most instructive problem of the build so far, worth documenting in full because the symptom pointed in completely the wrong direction.

When I reached Pcre2, its tarball (a .bz2) would not extract. tar ran, exited cleanly, and created no directory. No error, just nothing. The obvious first guess was a corrupt download, so I re-downloaded pcre2-10.47.tar.bz2 from the official source and verified it on the host: it decompressed and listed files perfectly. Yet inside the chroot, the same verified-good file still would not extract.

The diagnostic that cracked it was decompressing and counting bytes:

bzip2 -dc pcre2-10.47.tar.bz2 | wc -c

Inside the chroot this returned 0. On the host the same file returned 13 MB. So bzip2 in the chroot was producing nothing from a valid file. Running ldconfig then named the real cause directly:

File /usr/lib/libbz2.so.1 is empty, not checked.
File /usr/lib/libbz2.so.1.0.8 is empty, not checked.

The bzip2 shared libraries were 0 bytes. They had been installed empty back at section 8.7, which is why every bzip2 decompression silently produced nothing. The pcre2 file was never the problem.

The cause traced to the install step running as a mangled block where the terminal’s bracketed-paste handling corrupted the commands, leaving the cp of the shared libraries producing empty files. The fix was to rebuild Bzip2 cleanly, typing the install commands by hand one at a time, and verifying the library was non-empty immediately after the copy:

ls -l /usr/lib/libbz2.so.1.0.8

That showed 228,952 bytes, a real library. A fresh ldconfig then ran with no empty-file warnings, bzip2 decompressed the pcre2 tarball to its full 13 MB, and Pcre2 built without issue.

Two lessons from this:

First, a bad library install can surface as a completely unrelated symptom several packages later. A failed extraction looked like a network or download problem but was really a broken library from an earlier step.

Second, ldconfig reporting “File is empty” is the diagnostic that pins down a bad shared library install, and a quick ls -l on a library right after installing it catches the problem immediately instead of three packages downstream.


Snapshot

With Part 1 done, including the critical Glibc build, this is a good checkpoint before the toolchain rebuild in Part 2. Exit the chroot, unmount the virtual filesystems, verify clean with findmnt (only the /lfs root partition should remain), then snapshot.


What Is Next

Part 2 is the toolchain: M4, Bc, Flex, Tcl, Expect, DejaGNU, Pkgconf, then the final permanent Binutils, GMP, MPFR, MPC, and GCC with its full test suite. That is the heart of the chapter, rebuilding the compiler and binutils as permanent system components.


More updates to follow as each build phase completes.

Previous: Chapter 7: Entering Chroot and Building Additional Temporary Tools | Next: Chapter 8 Part 2: The Toolchain


All session notes and build logs are committed to the private repository at github.com/QuietWireDev/fathom-os.