LFS Temporary System
Chapter 4 builds the temporary system — the last packages compiled with the cross-toolchain, but now running for the future system and installed into it — and then performs the maneuver the whole setup was scaffolding: chroot into $LFS, where the partition becomes the root filesystem and the host ceases to exist for the build. After this chapter you're inside the system you're building — there's no longer any host to contaminate or be contaminated by.
Why a temporary system at all
The cross-toolchain (chapter 3) produces code for LFS, but it runs on the host — linked against the host's glibc, invoking the host's coreutils, the host's bash. To build the real system packages we need build tools that run inside the future system — compiled for x86_64-lfs-linux-gnu, linked against the fresh $LFS glibc. That's the temporary system: ~15 packages (M4, Ncurses, Bash, Coreutils, Diffutils, File, Findutils, Gawk, Grep, Gzip, Make, Patch, Sed, Tar, Xz, Binutils pass 2, GCC pass 2) cross-compiled and installed into $LFS — the minimal toolkit needed to build everything else once we're inside.
Pass 2 — binutils and gcc again, now for real
Binutils and GCC are built a second time — same tarballs, re-extracted fresh, but now --host=$LFS_TGT and linked against the new glibc. Pass 2's products land in /tools, and the build uses them — the pass-1 cross-toolchain's job is done.
cd binutils-*/build # fresh tree, fresh build dir
../configure --prefix=/usr \
--build=$(../config.guess) \
--host=$LFS_TGT \
--disable-nls \
--enable-shared \
--enable-gprofng=no \
--disable-werror \
--enable-64-bit-bfd \
--enable-new-dtags \
--enable-default-hash-style=gnu
make && make DESTDIR=$LFS install
# clean libtool archives that pin the cross path
rm -v $LFS/usr/lib/lib{bfd,ctf,ctf-nobfd,opcodes,sframe}.{a,la}
GCC pass 2 is the same shape — full languages, threads enabled, libstdc++ built properly — and it ends with the critical cc symlink:
ln -svf gcc $LFS/usr/bin/cc # packages call 'cc' by convention
The temp tools — the same ritual, ~15 times
Each temp package is the same pattern — cross-configure (--host=$LFS_TGT), make, make DESTDIR=$LFS install. A representative entry:
cd coreutils-*/
./configure --prefix=/usr \
--host=$LFS_TGT \
--build=$(build-aux/config.guess) \
--enable-install-program=hostname \
--enable-no-install-program=kill,uptime
make
make DESTDIR=$LFS install
# FHS placement fix-ups the book carries for several packages
mv -v $LFS/usr/bin/chroot $LFS/usr/sbin
mkdir -pv $LFS/usr/share/man/man8
mv -v $LFS/usr/share/man/man1/chroot.1 $LFS/usr/share/man/man8/chroot.8
sed -i 's/"usr\/bin"/"usr\/sbin"/' $LFS/usr/bin/chroot
The pattern repeats for M4, Ncurses (with its terminfo database and the libncursesw wide-char variants), Bash, Diffutils, File, Findutils, Gawk, Grep, Gzip, Make, Patch, Sed, Tar, Xz — each with its own handful of configure flags and FHS fix-ups. The book's chapter 6 is a checklist; the skill being taught is the ritual itself — by package ten you can nearly write the configure line yourself.
The chroot — the pivot
With the temporary system installed, $LFS now contains a self-sufficient Linux userspace: its own bash, its own coreutils, its own make, its own gcc. Enter it:
# as ROOT — back to root for the pivot
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)" \
/usr/bin/bash --login
Everything that follows runs inside. The prompt reads (lfs chroot) — the filesystem root is the partition. $LFS no longer exists as a path; it's now /. The /tools symlink trick pays off here: binaries compiled to look in /tools/... find exactly the directory they were built for.
But first — the virtual filesystems
A chrooted userspace needs the kernel's virtual filesystems or nothing works — /proc, /sys, /dev are kernel interfaces, not real directories. Before chrooting (or the book's equivalent ordering), they must be bind-mounted into $LFS from the host:
# as root — populate the device tree
mkdir -pv $LFS/{dev,proc,sys,run}
mount -v --bind /dev $LFS/dev
mount -v --bind /dev/pts $LFS/dev/pts
mount -vt proc proc $LFS/proc
mount -vt sysfs sysfs $LFS/sys
mount -vt tmpfs tmpfs $LFS/run
# shm + pts for modern packages
if [ -h $LFS/dev/shm ]; then mkdir -pv $LFS/$(readlink $LFS/dev/shm);
else mount -t tmpfs -o nosuid,nodev tmpfs $LFS/dev/shm; fi
This is the step people skip and then wonder why configure can't detect the system. Inside the chroot there is no /dev/null unless you bound it — testsuites, shells, everything needs it. The mounts are bind mounts — the host's kernel interfaces showing through into the chroot — so the temp system can interrogate the real hardware it's building for.
Ownership handover
Before chrooting the book changes ownership of the entire $LFS tree from lfs to root — inside the chroot, root is the builder:
chown --from lfs -R root:root $LFS/{usr,lib,var,etc,bin,sbin,tools}
case $(uname -m) in
x86_64) chown --from lfs -R root:root $LFS/lib64 ;;
esac
The lfs user's job ends here; the rest builds as chroot-root. (And later, the finished system's files get normalized to their real owners.)
The directory skeleton
Inside the chroot, the FHS skeleton is laid before packages:
mkdir -pv /{boot,home,mnt,opt,srv}
mkdir -pv /etc/{opt,sysconfig}
mkdir -pv /lib/firmware
mkdir -pv /media/{floppy,cdrom}
mkdir -pv /usr/{,local/}{include,src}
mkdir -pv /usr/lib/locale
mkdir -pv /usr/local/{bin,lib,sbin}
mkdir -pv /usr/{,local/}share/{color,dict,doc,info,locale,man}
mkdir -pv /usr/{,local/}share/{misc,terminfo,zoneinfo}
mkdir -pv /usr/{,local/}share/man/man{1..8}
mkdir -pv /var/{cache,local,log,mail,opt,spool}
mkdir -pv /var/lib/{color,misc,locate}
ln -sfv /run /var/run && ln -sfv /run/lock /var/lock
install -dv -m 0750 /root
install -dv -m 1777 /tmp /var/tmp
Then the essential first files — /etc/hosts, /etc/passwd and /etc/group (the temporary versions), plus the log files programs expect:
echo "127.0.0.1 localhost $(hostname)" > /etc/hosts
cat > /etc/passwd << "EOF"
root:x:0:0:root:/root:/bin/bash
bin:x:1:1:bin:/dev/null:/usr/bin/false
daemon:x:6:6:Daemon User:/dev/null:/usr/bin/false
messagebus:x:18:18:D-Bus Message Daemon User:/run/dbus:/usr/bin/false
uuidd:x:80:80:UUID Generation Daemon User:/dev/null:/usr/bin/false
nobody:x:65534:65534:Unprivileged User:/dev/null:/usr/bin/false
EOF
# /etc/group: root, bin, daemon, sys, kmem, tape, tty, daemon again...
touch /var/log/{btmp,lastlog,faillog,wtmp}
chgrp -v utmp /var/log/lastlog
chmod -v 664 /var/log/lastlog
chmod -v 600 /var/log/btmp
Where this chapter goes wrong
| Symptom | Cause |
|---|---|
Inside chroot: no such file for basic tools | Virtual filesystems not bind-mounted — /dev empty |
configure can't detect the system | /proc, /sys not mounted inside |
| Testsuites all fail | /dev/null, /dev/pts missing |
| Files owned by nobody/weird UIDs | Ownership handover skipped |
cc: command not found inside | The cc→gcc symlink skipped in gcc pass 2 |
| chroot complains "failed to run command" | /usr/bin/env path wrong in the chroot command, or bash not installed |
| Host binaries leaking in | PATH not reset in the env -i chroot line |
Exercises
- Explain why
/devmust be a bind mount rather thanmkdir. - From inside the chroot, prove you're isolated: compare
cat /etc/hostname,ls /home,dfagainst the host's. - Find three packages in the temp set with FHS fix-ups; explain one.
- Exit the chroot, re-enter it — write the full mount+chroot script from memory.
- Break
PATHinside chroot and observe which "commands" still work (builtins vs binaries).
Timeline
| Step | Result |
|---|---|
| binutils/gcc pass 2 | The real toolchain, LFS-linked |
| ~15 temp packages | Coreutils, bash, make… installed to $LFS |
| Virtual FS binds | /dev /proc /sys /run visible inside |
| Ownership handover | lfs → root |
| chroot | $LFS becomes / — you're inside |
| Skeleton | FHS dirs, passwd/group, log files |
| next | LFS III — the real system packages |
References
- linuxfromscratch.org — chapters 6–7: cross-compiling temporary tools; entering chroot
chroot(2),mount --bind— the kernel mechanisms- Filesystem Hierarchy Standard — the skeleton being laid