Skip to content
ALB Wiki

LFS Temporary System

This is an old revision of this page — revision 9 (2026-10-26 13:32:06 UTC). It may differ from the current revision.

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 install with --prefix=/usr under DESTDIR=$LFS — they land in $LFS/usr, part of the real system's layout, while /tools retires. The build uses them now — 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,gprofng,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.

The pass-2 configure grammar — the deep read

# binutils pass 2 — built for the new system, installed INTO it:
../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
rm -v $LFS/usr/lib/lib{bfd,ctf,ctf-nobfd,gprofng,opcodes,sframe}.{a,la}

--host=$LFS_TGT is the change — pass 2 runs on the new system; --enable-shared returns (now there's a libc); --enable-64-bit-bfd gives the full 64-bit object format. The .a/.la removal clears the static+libtool archives — the new system uses shared libs.

gcc pass 2 — the same shape: --host=$LFS_TGT, DESTDIR=$LFS, all languages enabled, threads on, libstdc++ real — plus ln -svf gcc $LFS/usr/bin/cc, the cc convention every package expects.

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 temp tools — the full roster and why each

The ~15 packages that constitute the temp system, and their jobs:

PackageThe job inside the chroot
M4Macro processor — autoconf's engine
NcursesTerminal library + terminfo database
BashThe shell everything's written for
Coreutilsls/cp/mv/sort/etc — the vocabulary
Diffutilsdiff — patch's partner
Filemagic-byte identification
Findutilsfind/xargs
GawkThe awk language
GrepPattern matching
GzipCompression — every tarball
MakeThe build driver
PatchApplies the book's diffs
SedStream editing
TarUnpacking everything
XzThe modern compressor
Binutils pass 2The real linker/assembler
GCC pass 2The real compiler + libstdc++

Each is the same cross-configure dance — --host=$LFS_TGT, make, DESTDIR=$LFS install — and several carry FHS fix-ups (binaries moved between /usr/bin//usr/sbin//bin for layout compliance). By the tenth you can nearly write the configure line yourself — that's the point.

The chroot environment — the deep read

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

Every piece earns its keep: env -i wipes the host's environment — no leaked variables inside; HOME=/root sets the chroot's home; PS1 marks the prompt (lfs chroot) so you know where you are; PATH resets to the new system's paths (no /tools — the temp tools are used implicitly); --login gives a full login shell. The TESTSUITEFLAGS parallelizes the package tests.

The re-entry ritual — leaving and coming back

You'll exit the chroot (reboot, sleep, fix the host) and must re-enter — the ritual:

# after any reboot — re-mount + re-bind + re-chroot
mount /dev/sda3 $LFS
mount --bind /dev $LFS/dev && mount --bind /dev/pts $LFS/dev/pts
mount -t proc proc $LFS/proc && mount -t sysfs sys $LFS/sys
mount -t tmpfs tmpfs $LFS/run
[ -h $LFS/dev/shm ] && mkdir -pv $LFS/$(readlink $LFS/dev/shm)
chroot "$LFS" /usr/bin/env -i HOME=/root TERM="$TERM" \
    PS1='(lfs chroot) \u:\w\$ ' PATH=/usr/bin:/usr/sbin \
    /usr/bin/bash --login

Write it as enter-lfs.sh — you'll run it many times. The build spans reboots; the chroot door works both directions.

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

SymptomCause
Inside chroot: no such file for basic toolsVirtual 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 UIDsOwnership handover skipped
cc: command not found insideThe 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 inPATH not reset in the env -i chroot line

Exercises

  1. Explain why /dev must be a bind mount rather than mkdir.
  2. From inside the chroot, prove you're isolated: compare cat /etc/hostname, ls /home, df against the host's.
  3. Find three packages in the temp set with FHS fix-ups; explain one.
  4. Exit the chroot, re-enter it — write the full mount+chroot script from memory.
  5. Break PATH inside chroot and observe which "commands" still work (builtins vs binaries).

Timeline

StepResult
binutils/gcc pass 2The real toolchain, LFS-linked
~15 temp packagesCoreutils, bash, make… installed to $LFS
Virtual FS binds/dev /proc /sys /run visible inside
Ownership handoverlfs → root
chroot$LFS becomes / — you're inside
SkeletonFHS dirs, passwd/group, log files
nextLFS III — the real system packages

References

  • linuxfromscratch.org — chapters 6–7: cross-compiling temporary tools; entering chroot
  • Hosted mirror: /dist/books/ — the official LFS book mirrored on this site (v10.0–13.1, SysV + Systemd editions; CC BY-NC-SA 2.0 / MIT, hosted verbatim with attribution)
  • chroot(2), mount --bind — the kernel mechanisms
  • Filesystem Hierarchy Standard — the skeleton being laid

See also

Categories: Handbook · LFS · Linux · Linux Handbook
This page was last edited 2026-10-26 13:32:06 UTC by ALB Team. What links here